23 Aralık 2020 Çarşamba

DOS'ta Klavye Düzenini Değiştirmek ve CD Sürücüyü Tanıtmak


Merhaba. Önceki dört yazı DOS üzerineydi ve DOS'la ilgili iki yazı daha planlıyorum. Bu yazıyı, yazı dizilerine kısa bir ara vermek için yazıyorum. MBR yazısında Norton disk editörün kullanımını anlatırken, Norton Utilities CD'si veya imaj dosyası olanların, bunu DOS'ta kullanabilmek için CD sürücüyü tanıtmaları gerektiğini yazmıştım.

DOS kurduktan sonra yapılacak işlerden biri de, klavye düzenini değiştirmek. Elbette olmazsa olmaz değil ama nasıl yapılıyor unutmamak için kendime de bir not düşeceğim. Sonuçta her gün yaptığım birşey değil.


DOS'ta Türkçe Klavye Düzeni
Klavyeyi değiştirmek, MSDOS ve FreeDOS'ta aynı mantıkla ama farklı yollarla yapılır. Standard ASCII karakter setinde ş ve ğ gibi karakterler bulunmaz. Bu nedenle önce "codepage"ler (CP) kullanılarak karakter setini değiştirmek gerekir.

ASCII karakter setinin ilk 128 karakteri sabittir. Standart hiçbir CP'de bunun değiştirildiğini görmedim. Teoride, bunların değiştirilmemesi için bir engel yok ama İngilizce alfabedeki 26 karakter ve rakamlar zaten dillerin çoğunda bulunur ve değiştirilmelerine gerek yoktur. CP'ler yardımıyla 128 ile 255 arası karakterler değiştirilir. Eski bazı CP'lerde ilk yarıdaki karakterlerden bazılarının değiştirildiğini okudum. Sanıyorum, örn. 7-bit ASCII destekleyen bir yazıcıda Almanca çıktı almak için az kullanılan karakterler, ü ve ö gibi karakterlerle değiştirilmiş.

Bir veya birden fazla CP, bir dosyada birleştirilip kullanıma sunulmuştur. Bu dosyalar DOS'ta .cpi uzantısına sahiptirler. FreeDOS'ta .cpi dosyalar upx'le sıkıştırılır ve .cpx uzantısını alırlar. Tüm CP'ler bir numarayla kodlanır. IBM PC BIOS'ta bulunan standart CP 437'dir. Bu arada farklı işletim sistemleri farklı numaralandırma kullandığından, çakışan numaralar olabilir. Ben burada DOS CP numaralarına örnek veriyorum. FreeDOS, CP 850'yi kullanıyor. Örn. 737 ve 851 Yunanca, 855 Kiril alfabesi, 857 Türkçe, 865 Danca ve Norveççe CP numaraları. Ayrıntılı liste Wikipedia'daki Code Page maddesinde bulunuyor.

Hangi CP'in hangi dosyada olduğunu bulmanın kolay yolu yok ne yazık ki. CPI dosyaları okumak için geliştirilmiş bir araç var ve içinde dökümantasyonu da içeriyor. Ayrıca başka bir dökümantasyon kullanarak kendi geliştirdiğim bir kod var ama yalnızca FreeDOS'un açılmış cpx dosyalarında denedim. Her CP içerisinde 3 tane 256 karakterlik bitmap var. İlki 8x16 pixel karakterler, ikincisi 8x14 karakterler ve üçüncüsü 8x8 karakterler.

Lafı uzatmadan, Türkçe karakterler için Türkçe CP'nin yüklenmesi gerek. Bunun için önce DISPLAY.SYS kullanılarak bellekte yer ayrılmalı. DOS'ta bu CONFIG.SYS içerisinden DEVICE veya DEVICEHIGH ifadeleriyle yapılıyor. Aşağıda, CON aygıtı olan ekrana CP yüklenebilmesi için 3 sayfa ayrılıyor.

DEVICE=C:\DOS\DISPLAY.SYS CON=(EGA,,3)

Yazıcılar için aynı iş PRINTER.SYS ile yapılıyor olmalı. DISPLAY.SYS tam anlamıyla bir DOS aygıt sürücüsü değil. Sanırım bu nedenle FreeDOS'ta DISPLAY.SYS yerine DISPLAY.EXE aynı işi yapıyor. Elbette bu, çalıştırılabilir dosya olduğundan AUTOEXEC.BAT'da çağırılmalı.

DISPLAY CON=(EGA,,3)

COUNTRY.SYS, klavye için mutlaka gerekli değil. Bu dosyada ülkelerin tarih, saat formatları ve para birimleri gibi bilgiler var. DATE ve TIME komutlarının çıktısını değiştirebiliyor. Bu dosyada da her ülke için üç haneli bir kod var. Örn. ABD 001, İngiltere 044, Almanya 049, Türkiye 090. Görünüşe göre birçoğu milletlerarası telefon kodlarıyla aynı. Daha geniş bir liste i8086.de adresinde var. Bu dosya yüklenirken ülke koduyla birlikte uygun bir CP seçimlik olarak verilebiliyor. Sanırım CP verilmediği durumda, o ülke için tanımlı ilk CP'yi kendisi buluyor. Fakat bildiğim kadarıyla COUNTRY.SYS, bir CP yüklemiyor. Açıkçası bu dosyaya parametre olarak neden CP girildiğini bilmiyorum. Bu dosya hem MSDOS hem FreeDOS'ta CONFIG.SYS'de çağırılmalı (bu arada FreeDOS'ta FDCONFIG.SYS var, CONFIG.SYS bunun içinden çağırılıyor):

COUNTRY=090,857,C:\DOS\COUNTRY.SYS
COUNTRY=090,,C:\DOS\COUNTRY.SYS

Ve elbette FreeDOS'ta bu dosya C:\FDOS\BIN altında.

Sonra yukarıda ayrılan belleğe, MODE komutuyla .cpi/.cpx dosyasından en az bir CP yüklenmelidir (PREP parametresi). Bu bir komut olduğundan AUTOEXEC.BAT'ta olmalıdır ama INSTALL ifadesiyle CONFIG.SYS'den de çağırılabilir. Sonra aynı komutla, bellekteki CP'lerden biri SEL parametresiyle seçilmelidir:

MODE CON CP PREP=((857) C:\DOS\EGA2.CPI)
MODE CON CP SEL=857

Birden fazla CP yüklemek için CP numaları ilk komutta virgülle ayrılarak yazılır. FreeDOS'ta CP 857, C:\FDOS\CPI\EGA.CPX dosyasında bulunur. FreeDOS, MSDOS .cpi dosyalarını kullanabilir. Tersi, FreeDOS'taki dosyalar sıkıştırılmış olduğundan mümkün değil, ancak açılırlarsa mümkün olabilir.

Bir CP seçildikten sonra ekran fontunun çok az değiştiği görülebilir. Artık Türkçe karakterler yüklemiş ve ekrana basılabilirdir. Şimdi tuşlara basıldığında üretilecek ASCII kodları değişmelidir. 'Ğ'ye basınca artık '[' karakteri çıkmamalıdır. Başka bir deyişle 1Ah scan kodlu tuşa basınca, ASCII 5Bh karakteri yerine 0A7h çıkmalıdır ama aynı zamanda ASCII 5Bh da 09 scan kodlu '8' tuşuna AltGr'yle basınca çıkmalıdır. Bunu yapan, KEYB komutuyla birlikte KEYBRD2.SYS'deki yeni eşleştirme tablosudur. Bir dosya verilmediğinde KEYB komutu KEYBOARD.SYS dosyasındaki klavye düzenlerini yükler. Dolayısıyla keyb de, keyb gr gibi komutlar sorunsuz çalışır. Türkçe klavye KEYBRD2.SYS dosyasında tanımlıdır. Bir dilde birden fazla klavye düzeni de olabilir. Türkçe F ve Q klavyeler gibi. Anladığım kadarıyla KEYB komutuna id parametresi verilmediğinde bulduğu ilk (veya varsayılan) düzeni yüklemektedir. FreeDOS'ta Türkçe için varsayılan düzen Q (id: 179) MSDOS'ta F'tir (id: 440). Dolayısıyla Türkçe Q klavye için, id MSDOS'ta zorunlu FreeDOS'ta seçimliktir.

KEYB TR,,C:\DOS\KEYBRD2.SYS /id:179

KEYBRD2.SYS, FreeDOS'ta C:\FDOS\BIN altındadır.

Bu komutların sonucunda Türkçe klavye kullanılabilir. Daha önce belirttiğim gibi COUNTRY.SYS yüklenmeden de klavye sorunsuz çalışır. Eğer DISPLAY eksik olursa MODE komutları çalışamaz. KEYB, hem DISPLAY hem de MODE'dan bağımsızdır ama gerekli CP'ler yüklenmeden çalıştırılırsa, 'Ğ'ye basıldığında ° (derece) karakteri çıkar. ü, ö ve ç karakterleri standart CP'de de bulunduğundan sorunsuz görünür ama 'İ' olmadığından Shift+i, 'ÿ' karakterini verir. Ancak sanıyorum ki, önce KEYB yüklenip ardından CP'ler yüklenebilir.


DOS'ta CD Sürücüyü Tanıtmak
DOS, normalde CD sürücüleri tanımaz. Bu yüzden Win95 CD'si olan bazı kullanıcıların formatlama macerasının sonu, sistem disketi CD'yi tanımadığından bilgisayarcıda bitmiştir. Dahası MSDOS bir CD sürücü dosyası da içermez. Win95 kapatıldıktan sonra da DOS, yanlış hatırlamıyorsam, CD sürücüyü göremez.

Öncelikle bir sürücü dosyası gerekli. Ne yazık ki o zamanki CD-ROM'ların çoğu bir sürücü disketiyle gelmezdi. IDE/ATAPI CD sürücülerin çoğu OAKCDROM.SYS ile basitçe tanıtılabilir. SCSI sürücüler için başka dosyalar olsa da komutlar aynı.

MSDOS'ta önce CONFIG.SYS'de DEVICE veya DEVICEHIGH ifadesiyle sürücü yüklenir. Parametre olarak /D: ile bir aygıt sürücüsü adı verilmelidir (sürücü harfiyle karıştırılmamalı, o sonra).

DEVICEHIGH=C:\CDROM\OAKCDROM.SYS /D:MSCD0001

Bu satırla, CD sürücü bir aygıt olarak bellekte yerini alır ancak henüz bir sürücü harfi atanmamıştır. Yukarıda verilen sürücü adı, AUTOEXEC.BAT'da MSCDEX'e /D: parametresiyle girilir. Bu şekilde CD sürücüye boştaki ilk sürücü harfi atanacağı gibi istenirse /L: parametresiyle başka bir sürücü harfi de verilebilir:

MSCDEX /D:MSCD0001 /L:E

MSCDEX'in açılımı Microsoft CD Extensions'tır. FreeDOS'ta, aygıt sürücüsü DEVLOAD komutuyla AUTOEXEC.BAT'ta yüklenir. /H parametresi HIGH, yani yukarı bellek alanı (DEVICEHIGH ile aynı), /Q parametresi 'Quiet' anlamındadır. Bu arada FreeDOS 1.2'de UDVD2.SYS adında bir sürücü dosyası gelmektedir.

DEVLOAD /H /Q C:\FDOS\BIN\UDVD2.SYS /D:FDCD0001

Ve son olarak FreeDOS'ta MSCDEX'e muadil SHSUCDX bulunur.

SHSUCDX /D:FDCD0001 /L:E

UDVD2.SYS, MSDOS'ta ve OAKCDROM.SYS FreeDOS'ta sorunsuz yüklenebilir. Ancak UDVD2.SYS'nin OAKCDROM.SYS'den küçük olması muhtemelen daha az bellek harcadığı anlamına gelmektedir. Bunun yanında UDVD2.SYS'nin yüklenme süresi de önemli ölçüde kısadır.

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: