basic/운영체제

[OS만들기] day 07: PIC(Programmable Interrupt Controller)의 초기화

NoelBird 2022. 11. 22. 13:34

오늘의 결과물

timer로 interrupt로 인해서 This is the timer interrupt 부분의 첫 글자가 계속 다른 글자로 변경됩니다.

저번 시간 간단 복습

뭔가 저번 시간에 했던 IDT가 잘 기억이 나지 않았습니다.

간단하게 요약하면, cli라는 명령어로 EFLAGS라는 레지스터의 interrupt 플래그를 0으로 만들어 줍니다.

그리고 IDT를 등록해서 인터럽트가 걸리면 어디로 갈 지 설정해줍니다.

이후, sti 명령어로 EFLAGS 레지스터의 interrupt 플래그를 1로 만들어줍니다.

interrupt 플래그가 1이면, 인터럽트를 받을 준비가 됐다는 의미입니다.

오늘의 코드

; init.inc
SysCodeSelector     equ 0x08
SysDataSelector     equ 0x10
VideoSelector       equ 0x18
; boot.asm
%include "src/init.inc"

[org 0]
    jmp 07C0h:start

start:
    mov ax, cs
    mov ds, ax
    mov es, ax

reset:              ; 플로피 디스크를 리셋합니다.
    mov ax, 0       ;
    mov dl, 0       ; drive=0 (A:)
    int 13h         ;
    jc reset        ; 에러가 나면 다시 합니다.

    mov ax, 0xB800
    mov es, ax
    mov di, 0
    mov ax, word [msgBack]
    mov cx, 0x7FF

paint:
    mov word [es:di], ax
    add di, 2
    dec cx
    jnz paint

read:
    mov ax, 0x1000          ; ES:BX = 1000:0000
    mov es, ax
    mov bx, 0

    mov ah, 2               ; 디스크에 있는 데이터를 es:bx 주소로
    mov al, 1               ; 1섹터를 읽을 것이라고 알림
    mov ch, 0               ; 0번째 실린더
    mov cl, 2               ; 2번째 섹터부터 읽기 시작합니다.
    mov dh, 0               ; Head=0
    mov dl, 0               ; Drive=0, A: 드라이브
    int 13h

    jc read; 에러가 나면 다시 함

    mov dx, 0x3F2   ; 플로피디스크 드라이브의
    xor al, al      ; 모터를 끈다.
    out dx, al

    cli

    mov al, 0x11    ; PIC의 초기화
    out 0x20, al    ; 마스터 PIC
    dw 0x00eb, 0x00eb   ; jmp $+2, jmp $+2
    out 0xA0, al    ; 슬레이브 PIC
    dw 0x00eb, 0x00eb

    mov al, 0x20    ; 마스터 PIC 인터럽트 시작점
    out 0x21, al
    dw 0x00eb, 0x00eb
    mov al, 0x28    ; 슬레이브 PIC 인터럽트 시작점
    out 0xA1, al
    dw 0x00eb, 0x00eb

    mov al, 0x04    ; 마스터 PIC의 IRQ 2번에
    out 0x21, al    ; 슬레이브 PIC가 연결되어 있습니다.
    dw 0x00eb, 0x00eb
    mov al, 0x02    ; 슬레이브 PIC가 마스터 PIC의
    out 0xA1, al    ; IRQ 2번에 연결되어 있습니다.
    dw 0x00eb, 0x00eb

    mov al, 0x01    ; 8086 모드를 사용합니다.
    out 0x21, al
    dw 0x00eb, 0x00eb
    out 0xA1, al
    dw 0x00eb, 0x00eb

    mov al, 0xFF    ; PIC에서 모든 인터럽트를
    out 0xA1, al    ; 막아놓습니다.
    dw 0x00eb, 0x00eb
    mov al, 0xFB    ; 마스터 PIC의 IRQ 2번을 제외한
    out 0x21, al    ; 모든 인터럽트를 막아둡니다.

    lgdt[gdtr]

    mov eax, cr0
    or eax, 0x00000001
    mov cr0, eax

    jmp $+2
    nop
    nop

    mov bx, SysDataSelector
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx
    mov ss, bx

    jmp dword SysCodeSelector:0x010000

    msgBack db '.', 0x67

gdtr:
    dw gdt_end - gdt - 1    ; GDT의 limit
    dd gdt+0x7C00           ; GDT의 베이스 어드레스

gdt:
    dd 0, 0
    dd 0x0000FFFF, 0x00CF9A00
    dd 0x0000FFFF, 0x00CF9200
    dd 0x8000FFFF, 0x0040920B
gdt_end:

times 510-($-$$) db 0
dw 0AA55h
; kernel.asm
%include "src/init.inc"

[org 0x010000]
[bits 32]

PM_Start:
    mov bx, SysDataSelector
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx
    mov ss, bx

    lea esp, [PM_Start]

    mov edi, 0
    lea esi, [msgPMode]
    call printf

    cld
    mov ax, SysDataSelector
    mov es, ax
    xor eax, eax
    xor ecx, ecx
    mov ax, 256     ; IDT 영역에 256개의
    mov edi, 0      ; 디스크립터를 복사한다.

loop_idt:
    lea esi, [idt_ignore]
    mov cx, 8       ; 디스크립터 하나는 8바이트이다.
    rep movsb
    dec ax
    jnz loop_idt

    mov edi, 8*0x20
    lea esi, [idt_timer]
    mov cx, 8
    rep movsb

    lidt [idtr]
    mov al, 0xFE        ; 막아두었던 인터럽트 중
    out 0x21, al        ; 타이머만 다시 유효하게 합니다.
    sti

    jmp $

;*****************************
;********** Subroutines ******
;*****************************

printf:
    push eax
    push es
    mov ax, VideoSelector
    mov es, ax

printf_loop:
    mov al, byte [esi]
    mov byte [es:edi], al
    inc edi
    mov byte [es:edi], 0x06
    inc esi
    inc edi
    or al, al
    jz printf_end
    jmp printf_loop

printf_end:
    pop es
    pop eax
    ret


;*********************************
;*********** Data Area ***********
;*********************************
msgPMode db "We are in Protected Mode", 0
msg_isr_ignore db "This is an ignorable inturrupt", 0
msg_isr_32_timer db "This is the timer inturrupt", 0

idtr:
    dw 256*8 - 1        ; IDT의 Limit
    dd 0                ; IDT의 Base Address

;*********************************
;*** Interrupt Service Routines **
;*********************************
isr_ignore:
    push gs
    push fs
    push es
    push ds
    pushad
    pushfd

    mov al, 0x20
    out 0x20, al

    mov ax, VideoSelector
    mov es, ax
    mov edi, (80*7*2)
    lea esi, [msg_isr_ignore]
    call printf

    popfd
    popad
    pop ds
    pop es
    pop fs
    pop gs

    iret

isr_32_timer:
    push gs
    push fs
    push es
    push ds
    pushad
    pushfd

    mov al, 0x20
    out 0x20, al

    mov ax, VideoSelector
    mov es, ax
    mov edi, (80*2*2)
    lea esi, [msg_isr_32_timer]
    call printf
    inc byte [msg_isr_32_timer]

    popfd
    popad
    pop ds
    pop es
    pop fs
    pop gs

    iret

;*********************************
;************* IDT ***************
;*********************************

idt_ignore:
    dw isr_ignore
    dw SysCodeSelector
    db 0
    db 0x8E
    dw 0x0001

idt_timer:
    dw isr_32_timer
    dw 0x08
    db 0
    db 0x8E
    dw 0x0001

times 512 - ($-$$) db 0

아직 코드만 작성했고, 이해하지는 못 했습니다.

특히 PIC(Programmable Interrupt Controller)라는 칩이 어떤 칩이고,

out 이라는 명령어를 통해서 어떻게 동작하는 것인지 잘 모르겠습니다.

i/o는 특별한 맵 같은 것이 있을 지도 궁금합니다.

왜 마스터 PIC가 0x20인지, 슬레이브 PIC가 0xA0인지 궁금합니다.

자세한 내용은 다음 시간에 알아보는 것으로 하겠습니다.

오늘의 명령어 정리

  • JC(Jump if Carry flag set): 캐리 플래그가 1일 때 JUMP
  • PUSHAD: 범용 레지스터 값들을 스택에 저장 (레지스터 백업하는 용도로 사용합니다.) Stack에 저장하는 순서는 다음과 같습니다. eax => ecx => edx => ebx => esp => ebp => esi => edi
  • POPAD: pushad의 반대 역할(스택에 들어있는 값들을 register로 pop하기)
  • PUSHFD: PUSH Flags Dword. x86 flags를 순서대로 dword 만큼 스택에 저장합니다.
  • PTR: 메모리에 접근해서 얼마만큼 읽어 들일것인지 데이터 타입을 재정의 하는 명령어입니다.

done

p. 124까지 입력 했음

내용 살펴보면서 이해하기