10 Aralık 2020 Perşembe

Keyboard Scan Code ve Keyboard Handler


Merhaba. Bu yazının konusu klavyenin nasıl çalıştığı. Bu kez başlık İngilizce çünkü internette "klavye tarama kodları" diye arattığımda yalnız bir kaynak bulunduğu için terimleri kafama göre Türkçeleştirmenin doğru olmadığını düşündüm. Bunun doğruluğu/yanlışlığına dair ayrı bir yazı çıkar, o yüzden bir kenara bırakıyorum.


Keyboard Scan Code
Klavyede bir tuşa basıldığında bilgisayara 'N' tuşuna basıldı '5' tuşuna basıldı gibi bir bilgi iletilmez. Zaten öyle olsaydı Türkçe, İngilizce, Rusça vs. klavye arasında geçiş yapamazdık. Bunun yerine klavye bilgisayara 1. tuşa basıldı, 8. tuşa basıldı gibi bir sıra numarası gönderir. Bu sıra numaralarına tuşların scan kodları denir. Kodlar, yalnızca tuşun (İngilizce) klavyedeki sıra numarasıdır. Örn. ESC: 1, 1'den Backspace'e kadar olan üst sıra 2 .. 14, Tab ile başlayıp Enter ile sonlanan ikinci sıra 15 .. 28 vb. Türkçe klavyedeki " (çift tırnak) karakteri üst sırada olsa da scan kodu 41'dir çünkü standart AT klavyede bu tuş orta sırada Enter'ın yanında bulunur. 

ESC'in AT klavyenin solunda olmasına rağmen scan kodunun 1 olması, aslında öncülü XT klavyede birinci sırada olmasından kaynaklanır. Dikkat edilirse iki klavyede de F11 ve F12 tuşları bulunmuyor. Buna sonradan da değineceğim.


Klavyenin Gelişimi
Bugün kullandığımız klavyenin standartları 1981'de piyasaya sürülen IBM PC'nin klavyesine (Model F) kadar gidiyor. Mart 1983'te piyasaya sürülen IBM PC XT'de de aynı klavye bulunuyor. Bu modellerde özelleşmiş bir klavye denetleyicisi (keyboard controller) bulunmuyor, dolayısıyla klavyeler programlanabilir değil. Programlanabilirlikten kasıt donanımın kendini test etmesi, resetlemesi veya tuş tekrarlama frekansı gibi parametlerin ayarlanması. Bu klavye yalnızca, anakarttaki 8048 entegresine basılan tuşun scan kodunu iletiyor.

1984'te piyasaya sürülen PC AT'de kullanılan klavyeyle (yukarıda görseli olan) birlikte IBM, önceden kullandığı kodları değiştiriyor ve anakarta 8042 klavye denetleyicisini ekliyor. Bu denetleyici yalnız klavyeden sorumlu ve klavyeye yukarıda saydığım programlanabilir özellikler ekleniyor. Ayrıca geriye doğru uyumluluk için klavyeye eklenen bir anahtarla klavye XT protokolünde çalıştırılabiliyor. SysRq tuşu da bu modelde ekleniyor.

Nisan 1986'da standart 101 tuşlu Model M klavye piyasaya sürülüyor. Insert, Home gibi tuşlarla F11, F12 tuşları ekleniyor. 


IBM Model M klavye (Kaynak: https://en.wikipedia.org/wiki/File:IBM_Model_M.png)

Ve son olarak Microsoft, Windows 95'le birlikte klavyelere 3 tuş daha ekliyor: Sağ ve sol Windows tuşuyla, menü tuşu.


Basma Kodları, Bırakma Kodları, Farklı Scan Kod Setleri
Klavye, bilgisayarla 60h ve 64h portu üzerinden haberleşir. 60h verinin, 64h denetleyici komutlarının iletildiği porttur. Klavyede bir tuşa basıldığında, anakarttaki denetleyiciye (8042) tuşun scan kodunu gönderir. Bu koda basma kodu (make code) adı verilir. Tuş bırakıldığındaysa bırakma kodu (break code) gönderilir. Ancak üç farklı kod standardı bulunur. 

Not: 60h aynı zamanda PS/2 farenin de bağlandığı porttur.

Set 1 de denen birinci standart, PC XT'de bulunan yukarıda açıkladığım standarttır. Bu, uyumluluk modu olarak bırakılmıştır. PC AT ile ortaya ikinci standart (set 2) çıkar. 8042 denetleyicisi, set 2 kodlarını set 1'e dönüştürür ve işletim sistemine iletir ancak bu davranış değiştirilebilir. Son olarak set 3, Ekim 1983'te IBM PC 3270 ile çıkmış ve set 2 ile kısmen uyumlu kodlardır. Set 3 de set 2 gibi 8042 tarafından birinci kod setine çevrilir. Kısaca, donanımın ilettiği farklı set kodlar işletim sistemi tarafında tek bir set gibi görünür. Klavyenin veri pinine osiloskop bağladığımda gördüğüm sinyalle 60h portundan okuduğum değer aynı değildir (XT klavyem yoksa).

Set 1'deki basma kodlarına yukarıda kısaca değindim. Bırakma kodları, basma kodlarının 80h ile VEYA işleminden geçirilmesiyle elde edilir. Örn. basma kodu 01h olan ESC'in bırakma kodu 81h, basma kodu 1Ch olan Enter'ın bırakma kodu 9Ch'dır. Bu hesapla bir klavyede maksimum 127 (sıfırıncı kod yok) tuş olabileceği düşünülebilir. Fakat bazı tuşlar için klavye birden fazla scan kod üretir. Genişletilmiş scan kod (extended scan code) adı verilen kodlar 0E0h ile başlar. Örn. nümerik klavyedeki Enter ve '/' (bölü) tuşu genişletilmiş scan kod üretir. Bunlara basıldığında sırasıyla 0E0h 1Ch ile 0E0h 35h üretilir. Genişletilmiş scan kodlarda, bırakma kodu ikinci byte'ın 80h ile VEYA işlemine sokulmasıyla elde edilir. Yani 0E0h 0B5h veya 0E0h 9Ch gibi. Bu arada genişletilmiş kodların hemen hemen tamamı 0E0h ile başlasa da bütün bir 0EXh bloğu aslında bunlara ayrılmıştır ve pratikte az sayıda da olsa 0E1h ile başlayan kod dizisi vardır.

Bu kodlar linux'ta showkey -s ile görülebilir. Örn:


Kodları DOS'ta görüntülemek için basit bir kod yazdım. Öncelikle belirtmeliyim ki, VirtualBox genişletilmiş scan kod setini tanımıyor. Kodu vmware'de geliştirdim ve test ettim. Kod, DOSBox'ta da düzgün çalışıyor ama DOSBox'ta TurboC'de kodu yazarken bazı garip hatalar aldım (bu benden de kaynaklanıyor olabilir). Ancak kod DOSBox'ta vmware'den bile stabil çalışıyor.

Yukarıdaki ekran görüntüsünde sırayla Enter, F10, F11 ve nümerik klavyedeki '/' ile Enter'a bastım. Bu çıktıdakiler elbette birinci sete çevrilmiş kodlar. ESC'e basarak programı sonlandırdım. Kodda 64h portuna yazdığım iki denetleyici komutu var. 0ADh klavyeyi durduruyor, 0AEh tekrar aktifleştiriyor. Bu arada klavyenin tarihinden bahsederken XT klavyelerde on fonksiyon tuşu olduğunu, F11 ile F12'nin AT'lerle birlikte eklendiğini söylemiştim. İşte bu nedenle F1 .. F10 arası tuşların scan kodları 3Bh ile 44h arasıyken F11'in scan kodu 57h ve F12'ninki 58h'dır. Bu programın kaynak kodu aşağıda:

#include <stdio.h>

unsigned char readport()    {
    unsigned char r;
    asm    {
        mov     dx, 0x0064   // klavyeyi disable et
        mov     al, 0xAD
        out     dx, al
        push    dx

        mov     dx, 0x0060   // klavye portundan oku
        in      al, dx
        mov     r, al

        pop     dx
        mov     al, 0xAE     // klavyeyi enable et
        out     dx, al
    }
    return r;
}


int  main(int argc, char* argv[])    {
    unsigned char r, r0;

    for( ;; )    {
        asm { cli }     // interruptlari kapat
        r = readport();
        if((r ^ 0xE0) < 0x10)   {
            // eğer porttan extended kod okunuyorsa
            printf("--- Ext. Key: %02X  ", r);
            // bir byte daha oku:
            printf("Escaped Char: %02X  \n", r0 = readport());
        }
        else if (r0 != r)       {
            // önceki basilan tustan farkliysa scan kodunu yaz
            printf("%02X\n", r);
        }

        if(r == 0x01)   {       // ESC ile cik
            asm { sti }
            break;
        }

        r0 = r;
    }

    return 0;
}

Çalıştırılabilir dosya ve kaynak kod buradan indirilebilir. 

Set 2'de, basılan tuş bir byte'lık kodlarla temsil edilir. Bu sette ESC 76h, '1' 16h, '2' 1Eh şeklinde karışık gider. Kodların klavyede nasıl göründüğü aşağıdadır: 

Kaynak: Wikibooks

Bu görseldeki 0F0h ile başlayan kodlar, set 2'deki bırakma kodlarıdır. Anlaşılacağı gibi bırakma kodu, basma kodundan önce gelen 0F0h ile temsil edilir. ESC 76h ise bırakma kodu 0F0h 76h'dır. Genişletilmiş kodlar bu sette de 0E0h ile başlar. Örn. sol windows tuşunun basma kodu 0E0h 1Fh ve bırakma kodu 0E0h 0F0h 1Fh'dir. 0E0h, set 1'deki gibi bırakma kodundan önce gelir. 

Set 3'te ilginç şekilde genişletilmiş tuşlar bulunmaz. Bunların görüntüsüne bu bağlantıdan; bu setlerle ilgili ayrıntılı bilgi ve dönüşüm tablosuna bu bağlantıdan ulaşılabilir. 

8042 klavye denetleyicisi normalde klavyenin türünü bulur ve klavyeden gelen tarama kodlarını dönüştürür. Güncel işletim sistemlerinin hiçbiri artık birinci seti kullanmamaktadır. Örn. linux'ta atkbd.c klavye sürücüsünde atkbd_select_set( ... ) fonksiyonu 2 veya 3 değerini döndürür. Bu fonksiyonun döndürdüğü değer atkdb struct'ı içerisindeki set elemanına yazılır.

8042'nin gelen veriyi hangi sete dönüştüreceği ayarlanabilir. 60h portuna 0F0h, 0 yazılarak mevcut set sorgulanır. Birinci, ikinci ve üçüncü setler için sırayla 43h, 41h ve 3Fh değerleri döner. Eğer 60h portuna 0F0h'ten sonra 1, 2 veya 3 yazılırsa 8042, istenen kod setinde veri göndermeye başlar. Elbette bunu DOS'ta yapınca, klavye sürücüsü başka setle gelen kodları anlamlandırmaya çalışacak ve yeniden başlatılıncaya kadar sistem kullanılmaz duruma gelecektir. Bu arada Linux'ta showkey -s komutu geçici olarak klavyeyi raw moda geçirir.


Klavye ile İletişim
Klavyeler, IBM PC AT'lere kadar beş pinli DIN konektörle bağlanıyordu. Bu klavyelerden bulmak sanıyorum bit pazarı haricinde imkansız. IBM, 1987'de PS/2 konektörünü geliştirdi. Bunu hatırlayanlar olacaktır, altı pinli, klavye için mor, fare için yeşil renkli bir konektördü. Bunlardan hala çöpe atılmamışsa, kilerde veya depoda tek tük bulunabilir. Günümüzdeyse hemen hepsinin USB bağlantısı var. 

PS/2 ve DIN konektörün sinyalleri tamamen aynıdır. İkisinde de Vcc, GND'nin yanısıra CLK ve DATA pinleri vardır. DATA'daki veri, CLK sinyaliyle senkron olarak (çift yönlü seri senkron iletim) gider. Her tuş basımında klavye CLK'yla senkron şekilde scan kodlarını DATA pininden seri olarak sürer. 

Bu bilgi, bilgisayarda birşey yapmak için gerekli olmasa da Arduino veya başka geliştirme kartlarıyla klavyeyi kullanmak için lazım olabilir.


Bir Tuşa Basıldığında Neler Olur?
Klavyede bir tuşa basıldığında scan kodunun denetleyiciye gönderildiğinden yukarıda bahsettim. Peki işlemci seviyesinde neler olur, ona bakalım. 

Öncelikle klavye denetleyicisi, birincil 8259 kesme denetleyicisinin (IRQ Controller) on dokuzuncu pinine (IRQ 1 bacağına) ulaşarak kesme isteğini işlemciye bildirir. İşlemci 8086 modundaysa Int 9'u çağırır, korumalı modda PIC programlanarak başka bir kesme çağırılır. 

Int 9 kodunu normalde BIOS yükler. İşletim sistemi gerekirse buna wrapper yazar veya tamamen değiştirebilir. BIOS'un çalışması bittiğinde (boot öncesi) Int 09 vektörü F000:E987 adresindedir. Bu kesme klavyeyi anlık olarak kapatır ve 60h portundan scan kodunu okur. Bundan sonra Int 15h/4Fh çağırılır. Int 15/4Fh, normalde bir tuşun scan kodunun değiştirilmesi gerekiyorsa kullanılır (örn. Fn tuşu ile Ctrl'nin yerini değiştirmek)*. Sonraki adımlar basılan tuşa göre değişkenlik gösterir. 
 
Shift, CTRL ve Alt tuşları için BIOS veri alanında (BIOS Data Area - BDA) bir flag bulunur. Bu flag 0040h:0017h ve 0018h adreslerindedir. Bu adreste CapsLock, NumLock, Pause vb. tuşlar için de flaglar bulunur.

Kaynaklar: Ralf Brown Int. List ve lowlevel.eu


Benzeri bir flag daha 0040h:0096h ile 0097h adreslerinde bulunur: 
 
Kaynak: Ralf Brown Interrupt List

Eğer basılan tuş yukarıda adı geçen özel tuşlardan biriyse, int 9 gerekli bitleri ayarlar. Örn. sağ shift'e basıldığında 36h scan kodu okunur ve BIOS 0417h'daki 0. biti set eder. Bu tuş bırakıldığında 0B6h scan kodu okunur ve BIOS 0417h'daki 0. biti resetler. 

Özel amacı olmayan bir tuşa basıldığında yalnız bu tuşa mı basılıyor yoksa bir tuş kombinasyonuyla mı basılıyor, önemlidir. Tuş kombinasyonları yukarıdaki flag'larla anlaşılır. Int 9, bir tuşa basıldığında bunun ASCII kodunu hesaplamak için bir tablodan yararlanır. Örn. basılan tuş yalnız 'A' ise, okunan scan kod 1Eh ve buna karşılık gelen ASCII kodu 'a'ya karşılık gelen 61h'dır. Ancak Shift+A basıldığında yine 1Eh scan kodu okunmasına rağmen shift flag aktiftir (öncesinde shift scan kodu gelmektedir ve flag set edilmiştir) ve int 9 bunun ASCII kodunu 41h ('A') olarak yorumlar. Aynı şey, shift flag aktif değilken CapsLock açıksa da gerçekleşir. Eğer Ctrl+A basılıyorsa, ASCII kodu 01h olarak dönüştürülür. Dolayısıyla BIOS'daki tablo 128 scan kod için 128 satırdan oluşur. Tuşun kendi değeri, Shift ile değeri, CTRL ile değeri için ayrı ayrı sütunlar eklenir. 

Scan kod - ASCII dönüşümü, hemen hemen her tuş için yapılır ve basılan tuş klavye ara belleğinde (keyboard buffer) depolanır. Fonksiyon tuşları, Home, PgUp gibi tuşların ASCII kodu olmadığından ara bellekte ASCII kodları 0 olarak depolanır. Ctrl, Alt, NumLock gibi tuşlarsa depolanmaz. Klavye arabelleği BDA'da 1Eh offsetinde bulunur ve 32 byte uzunluktadır. Her bir tuş için bir scan kod ve bir ASCII kod depolandığından 16 tuşa kadar burada saklanır. Bu veri yapısı bir "ring buffer" (Türkçesi olmayan bir terim daha) olarak kurulmuştur. Ring buffer'ın başlangıç adresi 1Ah ve bitiş adresi 1Ch adresinde bulunur. Bu bir ring buffer olduğundan 1Ah'daki adres 1Ch'deki adresten büyük olabilir. Bir tuş buraya alınacağı zaman tuşun scan ve ASCII kodundan oluşan word, 1Ch'nin gösterdiği adrese yazılır ve 1Ch'deki gösterici 2 arttırılır. Bu arttırım sonucu oluşan değer, ring buffer'ın sonu olan 3Eh adresini aşıyorsa bu değerden 20h çıkartılır. O zaman gösterici tekrar 1Eh adresini göstermeye başlar. Sonradan basılan tuş bellekte, önce basılan tuştan daha küçük offsette yer alabilir. Eğer arttırım sonucunda 1Ch'deki değer 1Ah'daki değere eşit olacaksa klavye arabelleği taşmıştır: Kullanıcı 16 tuşa basmış, BIOS bunları sıraya almış ancak işletim sistemi bunları bellekten oku(ya)mamıştır. Bazı BIOS'lar bu durumda anakarttaki minik hoparlörden sesle kullanıcıyı uyarırlar. Her okuma sonucu 1Ah'daki değer iki eksiltilir.

Buraya kadar scan kod porttan okunmuş, ASCII koduna çevrilmiş ve arabelleğe yazılmıştır. Bundan sonra klavye tekrar aktifleştirilir ve kesmeden çıkılarak ana programa geri dönülür.

Yukarıda anlattığım adımları VirtualBox'ın kaynak kodundan derledim. VBoxBiosAlternative386.asm VBox BIOS'unun assembly kod dökümü. Dosyanın en sağ sütununda, komutun olduğu bellek adresi ve kaynak kodunun olduğu dosya var. Int 9 kesme adresi olan F000h:E987h, bu dosyanın 18 268. satırından başlıyor ve kaynak kodu orgs.asm dosyasının 974. satırı. Klavyenin kapatılması, int 15h/AH=4Fh çağrısı burada gerçekleşiyor ve genişletilmiş scan kodlar için flag'lar burada ayarlanıyor (1011. satır). Sonra assembly kodundan, keyboard.c dosyasındaki int09_function fonksiyonu (371. satır) çağırılıyor. Bu fonksiyonun assembly kodu VBoxBiosAlternative386.asm dosyasında 7501. satırda. Bu fonksiyon, bir switch/case içinde, eğer basılan tuş, özel görevi olan bir tuşsa gerekli flag'ları ayarlıyor, normal bir tuşsa keyboard.c:90'da tanımlanan scan_to_scanascii yapısından tuşun ASCII ve scan kod karşılıklarını buluyor. Bu tablodaki her satır sırayla, tuşun normal, Shift'le, CTRL ile ve Alt ile basılması durumundaki kodları içeriyor. Son eleman 40h ise tuşun CapsLock'la, 20h ise NumLock'la durumunu değiştirdiğini belirtiyor. 604. satırda enqueue_key fonksiyonu çağırılarak çevrilen tuş klavye arabelleğine atılıyor. Bu fonksiyon da aynı dosyanın 339. satırında bulunuyor (VBoxBiosAlternative386.asm:7454).

Aslında BDA'nın 80h ve 82h offsetinde klavye arabelleği için birer tane daha başlangıç ve bitiş göstericileri var. Bu alan, XT BIOS'un 16 tuşluk kısıtlı ara belleğine alternatif olarak AT BIOS'larda tanımlandı. Ancak AT'ler çıktığında piyasada o kadar çok program eski ara belleği hardcoded olarak kullanıyordu ki, bu yeni göstericiler zamanla işlevsiz kaldı. Öyle ki, VBox bile bu göstericileri dikkate almıyor.

* DOS'ta bir klavye düzeni (örn. Türkçe klavye) yüklendiğinde Int 15h/4Fh, scan kodunu alıp ilgili klavye düzenine (layout) ait scan kod - ASCII kod karşılık tablosunda bulunup bulunmadığına bakar. Eğer kod bu tabloda varsa int 9 yerine kendisi tuşun scan ve ASCII kodlarını klavye arabelleğine sürer ve çıkışta carry flag'ı resetleyerek int 9'u bypass eder.


Klavye Arabelleğinden Okumak
Yukarıda herhangi bir şekilde klavye arabelleğinden okumaktan, basılan tuşu kullanıcı programına veya işletim sistemine iletmekten söz etmedim. Klavye portuna doğrudan erişim bir çok nedenle etkin bir yöntem değildir. Örn. klavye standart olmayan bir dilde olabilir ve tüm Alt, CapsLock vb. görevli tuşları kontrol etmeye çalışmak kodu gereksiz yere karmaşık hale getirir. BIOS, basılan tuşları arabelleğe attığı gibi arabellekten okumak için de fonksiyonlar sunar. Bu fonksiyonlar int 16h altında sunulur. 

Fonksiyonlardan ilki, AH=00 ile çağırılan sıfırıncı fonksiyondur. Görevi, klavye arabelleğinde tuş varsa, oradan bunu silmek ve AX'te döndürmektir. Arabellek boşsa fonksiyon, bir tuşa basılana kadar bekler. AH=01 ile çağırılan birinci fonksiyon, arabellekte tuş varsa bunu AX'te döndürür ancak arabellekten silmez. Tuş yoksa da sıfırıncı fonksiyonun tersine, girdi beklemeden sonlanır. Arabellekte tuş olup olmadığını zero flag aracılığıyla geri döndürür.

Yukarıdaki iki fonksiyon XT fonksiyonları olup XT tuş setiyle uyumludur. Başka bir deyişle AT klavyeyle gelen F11, F12, Home, Insert gibi tuşlar bu fonksiyonlarla okunamaz. AT serisiyle birlikte int 16h'ya 10h ve 11h fonksiyonları eklenmiştir. 10h fonksiyonunun görevi 00 ile aynı olup AT tuş takımındaki tuşları da okur. Aynı şekilde 11h fonksiyonu da 01 fonksiyonuyla uyumludur. 00 fonksiyonu çağırılıp F12'ye basılırsa herhangi birşey olmazken 10h fonksiyonu çağırılırsa F12'nin scan kodunu döndürecektir. 

Benzer biçimde 20h ve 21h fonksiyonları 122 tuş klavyeler için varsa da bu klavyelerin yaygın olmaması nedeniyle bu fonksiyonlar bir çok bilgisayarda bulunmaz.


Küçük Bir Uygulama
Klavye arabelleğiyle ilgili yazdıklarımı açıklamak için aşağıda küçük bir kod yazdım. Program sonsuz bir döngüde BDA'yı ekrana basıyor. Klavye arabelleği burada olduğundan, basılan tuşların arabelleğe girişi gerçek zamanlı görülebiliyor. Q, programı sonlandırıyor ve ESC arabelleği temizliyor. Kaynak kodu aşağıda:

#include<stdio.h>
#include<stdlib.h>

#define MEMLINES 10
#define MEMSIZE (MEMLINES * 16)

// return current page
unsigned char getpage()     {
    unsigned char p;
   
    asm     {
        mov     ah, 0x0F
        int     0x10
        mov     p, bh
    }

    return p;
}

// return current cursor position
void getline(unsigned char *x, unsigned char *y)    {
    unsigned char tx, ty;
    unsigned char p = getpage();

    asm     {
        mov     ah, 0x03
        mov     bh, p
        int     0x10
        mov     tx, dl
        mov     ty, dh
    }

    *x = tx;
    *y = ty;
}

// set given cursor position
void setline(unsigned char x, unsigned char y)      {
    unsigned char p = getpage();

    asm     {
        mov     ah, 0x02
        mov     bh, p
        mov     dl, x
        mov     dh, y
        int     0x10
    }
}

// listen on port 60h and return keypress and key release values
char keypress()     {
    unsigned char r;

    asm     {
        in      al, 0x60
        mov     r, al
    }

    return r;
}

// clear keyboard buffer by copying last key pointer over first
// key pointer i.e. 0040h:001Ch <-- 0040h:001Ah
void clearKBbuffer()    {
    unsigned char far *startKBbuffer = (unsigned char far *)0x0041C;
    unsigned char far *endKBbuffer = (unsigned char far *)0x0041A;

    *startKBbuffer++ = *endKBbuffer++;
    *startKBbuffer   = *endKBbuffer;

}


int main(int argc, char* argv[])        {
    int i, j;
    unsigned char curX, curY;
    // memblock points to the start of BIOS Data Area
    unsigned char far *memblock = (unsigned char far *)0x00400;

    getline(&curX, &curY);

    while(1)    {

        for(i = 0; i < MEMLINES; i++)   {
            // print offsets here:
            printf("%06X  ", memblock + (i << 4));

            // hexadecimal block is printed below:
            for(j = 0; j < 16; j++)     {
                printf("%02X ", memblock[(i << 4) + j]);
                if(j == 7)  printf("- ");
            }

            printf("  ");

            // char block is printed below:
            for(j = 0; j < 16; j++)     {
                    if((memblock[(i << 4) + j] < 32) ||
                       (memblock[(i << 4) + j] > 128))
                        printf(".");
                    else
                        printf("%c", memblock[(i << 4) +j]);
            }

            printf("\n");
        }
       
        setline(curX, curY);

        if (keypress() == 0x10)     // if Q key is pressed
            break;                  // break the infinite loop
        if (keypress() == 1)    {   // if ESC key is pressed
            clearKBbuffer();        // clear keyboard buffer
            // break;
        }
    }
   
    return 0;
}


Programı TurboC'de yazdım. TurboC'yi bulmak zor olabileceği için kaynak kodla çalıştırılabilir dosyası buradan indirilebilir. Program hem sanal makinada hem de DosBox'ta çalıştırılabiliyor (DosBox'ta prompt en alt satırdayken çalıştırınca getline() ve setline() düzgün çalışmıyor. cls komutundan sonra çalıştırınca çıktı düzgün görünüyor).

Programı ilk çalıştırdığımdaki ekran görüntüsü yanda. 1Ah ve 1Ch adreslerinde 38h bulunuyor. Birbirine eşit olmaları arabelleğin boş olduğu anlamına geliyor. Programın adını yazarken kullandığım readmem karakterleri arabellekte duruyor. Hatta 1Eh adresinde 'o' karakteri ve sonra ASCII kodu 9, scan kodu 0Fh olan Tab tuşu görülüyor. Komutu yazarken "project" yerine "pro" yazıp tamamlaması için Tab'a basmışım (FreeDOS). Ardından "readmem" yazıp tekrar Tab'a basmışım ama aynı dizinde hem readmem.exe hem de readmem.obj dosyası olduğundan yalnız nokta karakteri çıkmış. Ben de tekrar 'e' ve tekrar Tab'a basıp ardından 1Ch tuşuna yani Enter'a basarak programı çalıştırmışım. 1Ch, 37h offsetinde, bu nedenle de göstericiler 38h'yı gösteriyor. 

Sırayla 'wertyu' tuşlarına bastım. 'wer' 38h'dan arabelleğin sonu olan 3Eh'ya kadar olan alanda bulunuyor. Ardından ring buffer başa dönüyor, geri kalan 'tyu' tuşları 1Eh ile 24h arasında bulunuyor. Esc'e basarak arabelleği sıfırlayıp (1Ah'daki adresi 1Ch'deki adrese yazarak) sonrasında 'asdf'ye basıyorum (aşağıdaki görsel). Bunu yapmamla 'asd' tuşları önceden bastığım 'wer'in üzerine yazılıyor ve 'f' yine 1Eh adresine geliyor. Son olarak arabelleği temizlemeden 'Q'ya basıp ve programı sonlandırıyorum. Int 16h'yla tuşları okumadığım için program sonlanınca, bastığım asdf ve q tuşları DOS tarafından okunarak prompt'ta gösteriliyor.


Klavye bu kadarla sınırlı değil. Burada anlatılanlar yalnızca bir giriş olabilir. Klavyeyi açma ve kapatma komutlarının yanında daha onlarca denetleyici komutu bulunuyor. Bunlarla örn. klavye resetlenip donanım testi yapılabiliyor. PS/2 fare de belirttiğim gibi 60h portundan bağlı. Farenin tek farkı, klavye denetleyicisi IRQ1 oluştururken farenin IRQ12 oluşturması ancak farenin aktifleştirilmesi veya pasifleştirilmesi yine 64h portuna komut göndererek yapılıyor.


Kaynaklar: 
Metin içinde bağlantısı verilen kaynakların yanısıra:

Hiç yorum yok:

Yorum Gönder