8 Ağustos 2012 Çarşamba

Programlamabilir Kesme Denetleyicisi Gerçekten Programlanabilir mi?


Programlanabilir kesme denetleyicisi Türkçe windows'un çevirisinden alınma. Kulağa ileri okuma eniyileştirme gibi bir tını veriyor. İngilizcesi programmable interrupt controller yani PIC. Yazarken asıl mikrodenetleyici olan PIC serisiyle karıştırmamaya özen göstereceğim.

Programlanabilir kesme denetleyicisi, standartları neredeyse milattan öncesinden kalmış gibi gelen IBM PC uyumlu bilgisayarlarda mikroişlemci dışındaki aygıtlardan gelen kesme isteklerine cevap verebilmek için kullanılan 8259A çipine verilen ad. Bugün üretilen hiçbir anakart bu entegreyi kullanmıyor. Bunun iki nedeni var; birincisi zaten anakartların üzerinde1990'lardan beri kesme denetleyicisi için ayrı, DMA için ayrı, klavye denetleyicisi için ayrı ayrı entegreler bulunmuyor. Bunun yerine bunların hepsini kapsayan Southbridge çipseti içerisinde bütün entegrelerin karşılıkları var. İkincisi de 2000'lerde kesme çakışması yada başka bir deyişle IRQ çakışması hepimizi üzen bir sorundu. Eğer anakartınızda çok fazla genişleme kartı takılıysa mutlaka başınıza geliyordu çünkü toplamda 15 tane IRQ, zamanla gelişen bilgisayar teknolojisinin artan IRQ ihtiyacına yetmiyordu. Üstelik bir kaç seneye kadar artık masaüstü bilgisayarlarında bile birden fazla işlemci kullanılması kaçınılmaz görünüyordu ama 8259A ile bunu desteklemenin imkanı yoktu. Dolayısıyla Intel gelişmiş kesme denetleyicisini yani APIC'i çıkardı. APIC, 240 tane IRQ'yu destekleyecek şekilde üretilmişti ve mikroişlemcilerin birlikte çalışırken, cache bellek güncellemesi sırasında diğerinin çalışmasını durdurmasına olanak veren işlemciler arası kesmeyi (inter-processor interrupt -- IPI) destekliyordu. Ben genel olarak PIC'i anlatacağım kadar bildiğim kadarıyla APIC'in farklarından da bahsedeceğim.

Yalnız hepsinden önce birbirine benzeyen üç tane terimin bence açıklığa kavuşması gerekiyor:
  1. IRQ (Interrupt Request -- Kesme İsteği): Mikroişlemcinin dışındaki aygıtlar bir durum değişikliği yaşadıkları zaman "yahu birşeyler oldu, çalışmana bi ara ver de benimle ilgilen, ne olduğunu anla" anlamında mikroişlemciyi dürterler. Buna IRQ denir. En açıklayıcı örnek klavyeden bir tuşa basıldığında klavye denetleyicisi PIC'e bir kesme isteği iletir ki basılan tuş sistem tarafından okunsun, kullanılmayacaksa bile BIOS'un basılan tuşları tuttuğu tampon belleğine aktarılabilsin. Henüz kesme isteği PIC'dedir. PIC bunu mikroişlemciye iletir.
  2. INT (Interrupt -- Kesme): Mikroişlemciye PIC aracılığıyla iletilen kesme isteği eğer mikroişlemci daha önemli bir şeyle meşgul değilse bir kesme oluşturması gerekir. Kesme oluşursa adı gereği yapılan işlem kesilir, hangi kesme çağırılacaksa onun numarasına karşılık gelen kesme vektörü tablodan okunur ve CS:(E)IP'e yüklenir. 
  3. ISR (Interrupt Service Routine -- Kesme Hizmet Programı): Bir kesme gerçekleştiğinde devreye giren kesme koduna ISR denir. CS:(E)IP'ye yüklenen adresin başlangıcındaki koddan IRET (Interrupt Return) opkoduna kadar olan bloktur.

Kesme vektörleri Intel x86 işlemcilerinde gerçek modda (real mode) belleğin en başında bulunurlar. Bunlar aslında uzak göstericilerden (far pointer) başka birşey değildir. Örneğin resimde sıfırıncı kesme (sıfıra bölme kesmesi) gerçekleştiği anda mikroişlemci CS yazmacına (code segment) 00A7h değerini ve IP yazmacına (instruction pointer) 1068h değerini yükleyecektir.

Bu arada bir gereksiz bilgi daha: Aslında sıfırıncı kesme yalnızca sıfıra bölme hatası değil işlem sonucunda bulunan bölümün, bu bölümün depolanması için ayrılmış alana sığmaması sonucu da oluşur. DIV ve IDIV komutları bölen 8 bitlikse AX'deki değeri böler, bölümü AL'ye kalanı da AH'ye depolar; 16 bitlikse DX:AX'deki değeri böler ve bölümü AX'e kalanı da DX'e depolar. Örneğin aşağıdaki kod parçası sıfıra bölme olmamasına rağmen sıfırıncı kesmeyi tetikler:

mov dx, 0FFF
mov ax, dx
mov bx, 0001
idiv bx

Bunu yazmakla 'int 0' kodunu çağırmak yada yukarıdaki resim için 'jmp 00A7:1068' yazmak aynı şeydir. Turbo Pascal'ın CRT.TPU birimi, birim zamanda saniye üzerinden bir döngüye girip bir sayacı arttırıyor sonra da bulduğu değer üzerinden bölme işlemi yapıyordu. Pascal'ın CRT birimini kullanan programlar çok hızlı bilgisayarlarda (333 Mhz falan) çalışmamaya başladılar, sıfıra bölme hatası verip çıkıyorlardı. Çünkü bölmenin sonucu yazmaca sığmıyordu. (Sene 90'ların sonu) Sonradan bir CRT yaması çıktı da düzelmişti.

Bir gereksiz bilgi de, elbette ki yukarıdaki resim Windows XP altında alındığından, orada bir işlemcinin gerçek çalışma modundan bahsedemeyiz. Korumalı modda kesme vektörleri yerine kesme betimleyici tabloda (interrupt descriptor table -- IDT) bu vektörler tutuluyor.

Konudan yeterince uzaklaştıktan sonra tekrar PIC'e dönelim. Normalde IBM/PC için hangi IRQ'nun hangi INT tarafından karşılanacağı kabaca bellidir:
  • IRQ0: Programmable Interval Timer (PIT) Zamanlayıcı kesmesi. İstenirse PIT programlanarak belli zaman aralıklarında kesme üretmesi sağlanabilir. Eskiden anakarta bağlı hoparlörden bunu kullanarak ses çıkarırdık.
  • IRQ1: Klavye denetleyicisi. Daha önce de söyledim, her tuşa basıldığında bu kesme çağırılır.
  • IRQ2: Bu kullanılan bir kesme değildir. Aslında 8259A entegresi 8 tane kesmeye kadar bakabiliyordu ama bir tane daha 8259A buna bağlanarak (slave PIC) 15 tane kesmeye bakması sağlanıyordu. İkincil PIC birinciye bu IRQ2 bacağından bağlandığından gerçek anlamda bir IRQ2 bulunmamaktadır.
  • Geri kalan IRQ'lar seri ve paralel portlar, disk denetleyicisi ve ses kartı tarafından kullanılır. Bunları kullanmadığımdan hakkında pek bilgim yok.
IRQ0 oluştuğunda INT 08h çağırılır, IRQ1 oluştuğunda INT 09h çağırılır. İlk IRQ7'ye kadar bu sırada gider. İkincil PIC'in baktığı IRQ8--IRQ15 arasıysa INT 70h -- INT 77h arasındadır. Örneğin BIOS açılışta zamanlayıcıyı saniyenin 18.2'sinde IRQ0 oluşturacak şekilde programlar. (PIT'in zamanlayıcı taşması (Timer Overflow) bacağı PIC'in IRQ0 bacağına bağlıdır.) Her saniye 18.2 kere INT 08h çağırılır. BIOS aynı zamanda INT 08h'ya ait ISR'yi de belleğe ekler. ISR her çağırıldığında 0000:046Ch adresindeki DWORD'u bir arttırıp INT 1Ch'yi çağırır. DOS altında düzenli olarak çağırılan bir TSR yazarken INT 08h'nın kodunu değiştirmek sistemin kararlılığını etkileyeceğinden pek önerilmez, bunun yerine programın INT 1Ch'ya asılması önerilir.

Yalnız kutsal kitap Ralf Brown Interrupt List'e gözatınca INT 08h'nın aynı zamanda çifte hata (double exception) için de çağırıldığını görürüz. Benzer şekilde INT 09h matematik işlemci hatalarında "exception handler" olarak CPU tarafından INT 00h'ya benzer biçimde çağırılmaktadır. Özellikle IRQ5'i karşılaması gereken INT 0Dh aynı zamanda kullanıcılara çok tanıdık gelen genel koruma hatası (general protection fault -- GPF) ve yine IRQ6'yı karşılaması gereken INT 0Eh de aynı zamanda sayfa hatası (page fault) için çalışmaktadır. Şimdi Windows 95'in verdiği mavi ekranda "bir istisna 0E oluştu" ifadesinin ne anlama geldiği daha açıkça anlaşılabiliyordur sanırım.

Çok kötü birşeyler olmuş (Image credit)


Daha önce hizalama hatasından bahsederken aslında bu kesmenin de BIOS kesmeleriyle çakıştığını söylemiştim. Peki bu nasıl olabiliyor?

Birincisi sonradan eklenen INT 08h ile INT 11h arasındaki mikroişlemci istisnaları dikkat edilirse çoğunlukla korumalı moda ait hatalarda tetikleniyor. Dolayısıyla kodu, gerçek mod kodu olan INT 10h ve INT 11h kesmelerini zaten korumalı mod altında kullanmayacağımızdan yada INT 10h ile INT 11h gerçek modda tetiklenecek bir hata olmadığından gerçek moddayken güvendeyiz. Ama gerçek moddan korumalı moda geçtiğimizde bir IRQ5 gerçekleştiğinde ben gerçekte koruma hatası mı alıyorum yoksa sabit disk bir kesme mi istiyor bunu ayırt edebilmem gerçekten çok zor olacaktır. İşte programlanabilir kesme denetleyicisinin programlanabilir olduğu yer burası. Siz kesme denetleyicisine G/Ç portlarından ulaşarak yeniden programlayabilirsiniz. Kesme denetleyicisinin portları birinci denetleyici için 20h ikinci denetleyici için 0A0h'dır.
Bu portlardan denetleyiciyi programlayan kodu aşağıda veriyorum. Kod benim değil, zamanında Utrecht Üniversitesi'nin korumalı mod e-posta listesinde paylaşılmış basit bir kod:

mov    al, 00010001b ; Input Control Word (ICW) 1
out    20h, al       ; Birinci PIC
out    0A0h, al      ; Ikinci PIC

mov    al, 20h       ; IRQ0'i karsilayacak INT
out    21h, al
mov    al, 28h       ; IRQ8'i karsilayacak INT
out    0A1h, al
 
mov    al, 00000100b ; ICW3
out    21h, al       ; Ikinci PIC hangi bacaga bagli?
mov    al, 2
out    0A0h, al

mov    al, 00000001b ; ICW4
out    21h, al
out    0A1h, al

mov    al, 11111011b ; Output Control Word (OCW)1: bir olan IRQ'lari maskele
out    21h, al
mov    al, 11111111b ; hepsini maskele
out    0A1h, al

Benzeri kodlar http://wiki.osdev.org/8259_PIC adresinde de bulunabilir.

PIC'i programlarken dikkat edilmesi gereken ICW1 20h portuna yazılırken ICW2, ICW3 ve ICW4 komutları ICW1'in yazılması bittikten hemen sonra 21h portuna yazılması gerekiyor.

ICW1'in ilk dört biti 0001 olması gerekiyor. Üçüncü bit PIC'i kenar tetiklemeli (edge-triggered) moda alıyor, ikinci bit kesme vektörlerinin 32 bit olduğunu bildiriyor, birinci bit PIC'in birlikte çalıştığı başka bir PIC daha bulunduğunu bildiriyor. Sıfırıncı bitse ICW4'ün verileceğini bildiriyor.

ICW2, birinci ve ikinci PIC'lerin oluşturacağı IRQ'ları mikroişlemcide hangi kesmenin karşılayacağını bildiriyor.

ICW3, birinci PIC'in hangi bacağına bağlı olduğunu bildiriyor. (IRQ2) İkinci PIC içinse ICW3 bu PIC'e bir numara vermemizi sağlıyor. Bu numara birden fazla PIC ardarda bağlanırsa hepsini numaralandırabilmeye olanak tanıyor. IBM PC uyumlularında yalnızca iki tane olduğundan çok da önemli değil.

ICW4'ün sıfırıncı biti 8086 modunda kesmeler üretmesini sağlıyor.

Kodun son parçasında OCW aracılığıyla birinci PIC'de IRQ2 haricindeki IRQ'lar iptal ediliyor ve ikinci PIC'de de bütün IRQ'lar iptal ediliyor. Diğer bitlerin değerleri ve anlamları için yine Ralf Brown kesme listesine bakılabilir.

Elbette ki bu kod çalışırken herhangi bir kesme gerçekleşse bile dikkate alınmaması gerekiyor bu nedenle bu kodun hemen öncesinde bir CLI komutu çalıştırılmalı ve sonrasında gerekmiyorsa (tabi ISR'ler hazırlandıktan sonra) kesmeler yeniden STI ile geri getirilmelidir.

Kesme denetleyici entegreye bakınca 8 bitlik bir veriyolu bağlantısının olduğu görülür. Bir IRQ gerçekleştiği zaman kesme denetleyicisi mikroişlemcinin INTR (Interrupt Request) bacağını set edip veriyoluna kesmenin numarasını sürer. Bundan sonrasında mikroişlemcinin keyfi olursa o kesmeyi çağırır ve INTA bacağını resetler (INTA active low olması lazım) yada kesme isteğine cevap vermez.

Son olarak sinyallemenin daha rahat anlaşılması için google görsellerde "Cascaded 8259" aratıldığında bağlantının nasıl yapıldığını anlatan güzel görseller bulunabiliyor. Ben, başkasının çizimleri olduğundan buraya eklemek istemedim.