Olen kirjoitellut viime aikoina paljon x86-64-arkkitehtuurin ohjelmoinnista Long Modessa eli täydessä 64-bittisessä moodissa. Kokeiluissani pysäytin aina keskeytykset ohjelman alussa (CLI).

Keskeytyksiä kuitenkin tarvitaan heti, kun halutaan esimerkiksi lukea näppäimistöä tai ajastaa toimintoja. IRQ0 (System Timer / INT 0x08) on yksinkertainen kello, joka laukeaa 18.2 kertaa sekunnissa. IRQ1 (Keyboard / INT 0x09) puolestaan laukaistaan aina käyttäjän painaessa jotain näppäintä.

Jos IRQ:t (Interrupt ReQuest) eivät ole tuttuja, niin ne ovat 8259 PIC -kontrollerin generoimia laitteistokeskeytyksiä. IRQ:t 0-7 laukaisevat keskeytykset 0x08-0x0f ja IRQ:t 8-15 taas keskeytykset 0x70-0x77. Näiden lisäksi prosessori käyttää sisäisesti keskeytyksiä 0x00-0x1f. Kaikki muut keskeytykset ovat ohjelmoijan vapaasti käytettävissä ja kutsuttavissa INT-komennolla. Esimerkiksi Linuxin system callit käyttävät keskeytystä INT 0x80.

IDT - Interrupt Descriptor Table

Long Modessa keskeytyksille (INT 0x00-0xFF) rekisteröidään käsittelijät IDT:hen eli Interrupt Descriptor Tableen. Se sisältää 256 kappaletta 128-bittisiä kenttiä, jotka määrittelevät käsittelijöiden muistiosoitteet sekä muutamia lippuja. Kenttien rakenne on kiusallisen hankala (löytyy dokumentaatiosta) ja sen kanssa pitää olla tarkkana. Varsinkin assemblyä ohjelmoidessa bittien nyplääminen on välillä tuskallista.

Tässä on esimerkkikoodia, joka asettaa käsittelijän INT 0x00:lle. Se olettaa, että GDT:hen on määritelty koodisegmentti KERNEL64_CODE, jossa keskeytysten käsittelijät majailevat:

init_idt_64:                            ; setup ISR gate descriptors
        mov rdi, idt64
        mov rax, isr_00
        call set_idt_desc
        lidt [idt64_desc]               ; load 64-bit IDT
        sti
        hlt
        jmp $

set_idt_desc:                           ; routine to set IDT descriptor at RDI to handler at RAX
        push rax
        push rbx
        push rcx
        push rdx

        ; Build the lower 64-bit word in RBX

        mov rbx, rax                    ; target offset 31-16 (0x00000000ffff0000) at low 0xffff000000000000
        shl rbx, 32
        mov rcx, 0xffff000000000000
        and rbx, rcx

        mov rcx, 0x00008e0000000000     ; flags (16 bits) at low 0x0000ffff00000000
        or rbx, rcx                     ; flags: P:1(1) DP:2(00) 0:1(0), Type:4(0x0e), Reserved:5(00000), IST:3(000)

        mov rdx, KERNEL64_CODE          ; target selector (16 bits) (0x000000000000ffff) at low 0x00000000ffff0000
        shl rdx, 16
        mov rcx, 0x00000000ffff0000
        and rdx, rcx
        or rbx, rdx

        mov rdx, rax                    ; target offset 15-0 (0x000000000000ffff) at low 0x000000000000ffff
        mov rcx, 0x000000000000ffff
        and rdx, rcx
        or rbx, rdx

        ; Build the higher 64-bit word in RDX

        mov rdx, rax                    ; high 0xffffffff00000000 must be zero
        shr rdx, 32                     ; target offset 64-32 (0xffffffff00000000) at high 0x00000000ffffffff

        mov rax, rbx                    ; First store lower 64-bit word
        stosq

        mov rax, rdx                    ; Then the higher 64-bit word
        stosq

        pop rdx
        pop rcx
        pop rbx
        pop rax
        ret                             ; at end, RDI will point to the next descriptor

align 8
isr_00:
        iretq

align 8
idt64:                                  ; 64-bit Interrupt Descriptor Table
        times 256 dq 0x0000000000000000 ; space for 256 x 128-bit descriptors
        times 256 dq 0x0000000000000000
idt64_end:

align 8
idt64_desc:                             ; 64-bit Interrupt Descriptor Table info
        dw idt64_end - idt64 - 1        ; IDT length (16-bit)
        dq idt64                        ; IDT location (64-bit)

IRQ1 - Keyboard Data ready

Laitteiston generoimien keskeytysten käsittely tapahtuu normaalisti näin:

  1. Käsitellään tapahtuma (esim. luetaan näppäimistöltä painettu näppäin)
  2. Kuitataan keskeytys PIC-kontrollerille
  3. Poistutaan käsittelijästä IRETQ-komennolla

Tässä esimerkkikoodi, joka käsittelee IRQ1/INT 0x09:n ja lukee näppäinten painallukset:

isr_09:                                 ; IRQ1 - Keyboard Data Ready
        call read_keyboard
        mov al, 0x20                    ; acknowledge interrupt
        out 0x20, al
        iretq

read_keyboard:
        push rax
        push rbx
        push rsi

        in al, 0x64
        and al, 0x01
        jz .end
        in al, 0x60
        mov bl, al
        and bl, 0x80
        jnz .end
        .keydown:
        mov byte [0x00000000000b8000], al
        <process key press...>
        .end:
        pop rsi
        pop rbx
        pop rax
        ret

Koodi lukee scancodet AL-rekisteriin, ja siitä voi lähteä sitten mäppäämään koodeja merkkeihin ja käsittelemään syötettä. Näytöllehän on tekstitilassa helppo tulostaa käytämällä suoraan 0x00000000000b8000-osoitetta.

Published 23.8.2009