Saturday, October 13, 2007

Taşındım

Wordpress'e döndüm. Yeni adresim.

Sunday, February 25, 2007

Kenar (edge) ve seviye (level) tetiklemeli kesmeler üzerine

Çekirdek için donanım ile yazılım arasında bir arayüz diyoruz. Genelde donanım ile alakalı kısımları okurken, donanım hakkında bir fikir sahibi olmanız gerekiyor ki çekirdeğin nereye varmaya çalıştığını anlayabilesiniz. Bunun için de donanım speklerine ihtiyaç duyarsınız. Ama bazen bu spekler de yetersiz kalır, çünkü spekler size o donanımı nasıl kullanabileceğinizi anlatırlar. Fakat sizin ihtiyacınız olan, o an baktığınız sistemde o donanımın ne şekilde kullanılmış olduğu bilgisidir.

ISA'da edge-triggered kesmeler, PCI'da level-triggered kesmeler kullanıldığını ve bu yüzden PCI bus'larda kesme paylaşımı yapılabileceği ancak ISA'da kesme paylaşımı yapılamayacağını duymuş ama nedenini merak ediyorsanız, hatta ISA'da aynı zamanda kesme paylaşımı yapılabileceğini duymuş, hayda hani yapılamıyordu diyorsanız, ya da edge/level triggered ne ola ki diyorsanız okumaya devam edebilirsiniz.



Dijital sistemlerde kesmeler sinyalin farklı zamanlarına bakılarak tetiklenebilir. İki çeşit tetikleme mekanizması bulunmaktadır: edge (kenar) ya da level (seviye) tetiklemeli. Kenar tetiklemeli kesmeler, sinyalin 1'den 0'a ya da 0'dan 1'e geçtiği anların tespiti ile; seviye tetiklemeli kesmeler, sinyal'in 0 ve ya 1 seviyesinde bulunduğu anlara bakılarak yapılır.

Kenar (edge) ve seviye (level) tetiklemeli kesmeler ve sorunları üzerine konuşacağımız için Intel'in 8259 entegresi bu konu için ideal. 8259'un datasheet'ine buradan ulaşabilirsiniz.

Kesme Süreci

x86 mimarisindeki bir işlemcide "genel" amaçlı kesmeler için istenildiğinde maskelenebilen INTR pin'i kullanılıyor. Pin dışarıdaki bir kesme kontrolörünün çıkışına bağlanıyor. Kontrolör tek bir 8259 entegresi olduğu durumda 8 farklı cihaz da bu kontrolörün girişine bağlanabiliyor.

8259 kontrolörü içerisinde kesme isteklerini işleyen iki odacık olduğunu düşünebiliriz. İlk oda kabul odası (IRR), ikinci oda ise servis odası (ISR). Her hangi bir girdiden kesme geldiği anda IRR odasında beklemeye alınıyor ve özel bir süreç işlemeye başlıyor. Bu süreç şu şekilde işliyor:

1. Kontrolör girişlerinden bir pin (IR7-0) yüksek değere çekilir. Kesme geldi.
2. 8259 bunu farkeder ve işlemci'ye INT gönderir.
3. İşlemci, INTA pin'i ile kontorolöre ack sinyali gönderir.
4. IRR'deki yüksek öncelikli bit (0-7) ISR'ye alınır ve IRR'deki bit temizlenir.
5. İşlemci ilk INTA'nın ardından ikinci bir INTA gönderir. Bu sefer 8259, 8-bitlik veri hattına kesme vektör bilgisini yerleştirir. İşlemci bu vector bilgisini IDT (Interrupt Descriptor Table) ile birlikte kullanarak o kesme için işaret edilen adresteki kesme işleyicisini (interrupt handler) çalıştırır.
6. IRR, ilk INTA sonunda resetlenmişti, ikinci INTA sonunda da ISR resetlenmeli. Bu, kesme'nin görevini yerine getirdiğinin bir işareti: End-Of-Interrupt (EOI). 8259 Auto EOI modunda ise bu biti ikinci INTA sonrası kendisi temizliyor, eğer değilse kesme işleyicisinin ilgili ISR bit'i için 8259'a EOI komutu göndermesi gerekiyor.

Kenar Tetiklemeli Kesmeler (Edge-Triggered Interrupts)

ISA veriyolu kenar tetiklemeli kesmeler kullanılmak üzere tasarlanmış. Anakart üzerinde bir yerlerde 8259 kesme kontrolörü kenar tetiklemeye duyarlı olarak programlanmış şekilde bekliyor. Kontrolör herhangi bir kesme girişinden sinyal'in düşük seviye'den yüksek seviyeye geçtiğini algıladığı anda işlemci'ye kesme gönderecek. Buna göre şu şekilde çalışan bir ISA cihazı düşünebiliriz. Cihaz normalde kesme hattını düşük seviyede, kesme göndereceği zaman da kesme hattını yüksek seviyede sürecek. Kesme gönderdikten sonra kesme işleyicisinden cihaz'ın kesme göndermesine sebep olan neyse bu durum çözümlenecek ve ya ileride halledilmek üzere bu bilgi not edilecek, sonra da kesme hattını tekrar düşük seviyeye çekmesi kesme işleyicisi (sürücü) tarafından sağlanacak. Bunun nasıl yapılacağı cihazdan cihaza değişiklik gösterebilir.

Kenar tetiklemeli olmasına rağmen 8259'un tasarımı gereği ilk INTA geldikten sonra hattın aktif tutulması gerekiyor. Bunun sebebi tetiklemeyi yapan sinyal'in bir gürültü (noise) olabilme olasılığı. Kesme olmadığı zamanda düşük seviyede olan hatta oluşabilecek bir gürültü tetiklemeye yol açabilir. Hat o an aktif değil ise ikinci INTA'da işlemciye özel olarak 7 vektörü dönülüyor. Buraya gerçekten bir cihaz bağlı ise, kesme işleyicisi'nin kesme'nin gerçek olup olmadığını cihaz ile konuşarak kontrol etmesi lazım.

Bu şekilde tasarlanmış kesme çıkışları ile yani hattı hem düşük, hem yüksek süren cihazların ikisi aynı hattı paylaşamaz.

Kenar Tetiklemeli Kesme Paylaşımı (Edge-Triggered Interrupt Sharing)

Kenar tetiklemeli kesmeleri paylaştırabilmek için şöyle bir yöntem kullanılabiliyor: Öncelikle anakart üzerinde ve ya kontrolörün içerisinde (bazı üretici modellerinde mevcut değil) her bir kesme hattı bir pull-up direnci ile yüksek seviyede tutuluyor. Paylaşım yapacak cihaz, kesme göndereceği zaman hattı bir süre düşük seviyede sürüyor, hattı sürmeyi bıraktığı anda hat otomatik olarak pull-up direnci sayesinde yüksek seviyeye çıkıyor. Bu şekilde kesme tespiti için gerekli kenar tetiklemesi sağlanmış oldu.

Hattı ortak kullanan diğer ISA cihazları da kesme göndermek istediklerinde hattı yalnızca düşük seviyeye çekip bırakacakları için aynı anda iki cihazın hattı kullanmasında bir sorun oluşmuyor ve hat paylaşımı yapılabiliyor. Hattı ne kadar süre düşük seviyede tutacakları için de bir standart olmadığı için, bu da cihazdan cihaza değişebiliyor. Bir cihaz düşük seviyede tuttuğu esnada bir başka cihaz da düşük seviyeye getirip hattı sürmeyi bırakabilir örneğin, halen ilk cihaz sürmeyi bırakmadıysa da ikinci cihazın kesmesi arada kaybolacaktır. Kenar tetiklemeli kesmelerde her zaman kesme işleyici zincirinin tamamının gezilmesi kritik aralıklarda kaybolabilecek kesmelerin işlenebilmesi sebebi ile yapılmalı.

Seviye Tetiklemeli Kesmeler (Level-Triggered Interrupts)

Seviye tetiklemeli modda, kesmeler kontrolörün hattın düşük ve ya yüksek seviyede bulunmasına bakarak tetikleniyordu. PCI spekleri kesmelerin bu şekilde olması gerekliliği koşulunu getiriyor. PCI cihazlarda kesme üretebilecek dört ayrı pin (INTx#) bulunuyor. Ve bu pinler aktif-düşük olarak kuzey köprüsünün aktif-düşük pinlerine bağlanıyor. Kuzey köprüsüne göre kesme girdi sayısı farklılık gösteriyor. Fakat köprünün aktif-düşük girişlerini ortak kullanan PCI cihazlarının INTx pinleri, köprü tarafından belirli ve ya programlanabilir şekilde kesme kontrolörünün pinlerine route edilerek ve de aktif-yüksek sinyale çevrilerek iletiliyor.

PCI cihaz kesme ileteceği zaman kesme hattını düşük seviyede tutuyor bu anda kesme tespit edilerek kesme işleyicisine ulaşılıyor. Buradan da cihazın isteği karşılandıktan sonra yine kesme işleyicisi tarafından cihaz hattı bırakıyor. Eğer hat halen bir cihaz tarafından aktif olarak tutuluyorsa, yeniden kesme kontrolörü kesme gönderiyor. Seviye tetiklemeli kesmelerin avantajı, hat aktif olduğu sürece tespit edilebiliyor olması. Kenar tetiklemeli kesmelerin kısa bir aralıkta tespit edilmesi gerekiyor, kesme kalıcı değil ve kritik aralıklarda da kaybolabiliyor.

Sorunlar

Edge-triggered kesme üreten cihazların bir kaç şekilde tasarlanabildiğini söyledik. İlk zamanlar da cihazın kesme hattını tek başına kullanılacağı düşünülerek çıkışlar "totem-pole" ya da "tri-state buffer" ile sürülmüş. Hat üzerinde bu şekilde tasarlanan birden fazla cihaz bulunursa ve her bir cihaz hattı aynı anda sürmeye kalkarsa veriyolu üzerinde kavgaya "bus fight" tutuşacaklardır. Belki kesme çıkışlarında "tri-state buffer" lar kullanan ama aynı anda kesme üretimi yapmayacağı garanti olan cihazlar hattı sürmedikleri zamanlarda çıkışlarını sürmeyerek yüksek empedansda bırakabilirler. Bu şekilde bir ihtimal paylaşımları mümkün olabilir.


Edge-triggered kesme üreten cihazların çıkışları "open-collector" yapıldığı takdirde ve hat üzerinde bir pullup direnci ile her zaman yüksek seviyede tutulması ancak kesme üretileceği zaman düşük seviyede bir süre tutularak hattı bıraktığı anda hattın yeniden yüksek seviyeye çıkması ile ("pullup direnci ile") tetikleme yapılabilir. Bunun dışında çıkışta "tri-state buffer" kullanılarak da paylaşım sağlanabilir. Hat yalnızca
ya düşük seviyede sürülür ya da yüksek empedansda tutulur. ("float") Bu şekilde tasarlanmış bir ISA cihaz örneğini şu adreste bulabilirsiniz.

PC XT mimarisi zamanında kullanılmaya başlanan ISA veriyolu yapısı başta kesme paylaşımına olanak verecek şekilde tasarlanmamış. Cihazların kesme süren kısımlarının da belirli bir standardı bulunmadığı için üreticiler farklı yöntemlerle cihazlar üretmişler. Bu yüzden kenar tetiklemeli ISA cihazlarda kesme paylaşımı pratikte mümkün olabilse de, çoğunlukla paylaşım yapılamıyor demek daha doğru. Özellikle anakart üzerinde sistem saati gibi sabit legacy kesmelerin paylaşımı mümkün değil. Sistem saati direk kesme kontrolörüne bağlı.

Aynı üreticinin ürettiği ve yaklaşık aynı işi yapan fakat tasarım kararları sebebiyle paylaştırılamayacak iki ISA cihazın "hardware hacking" ile paylaştırılabilir hale getirildiği hoş bir örneği şu adreste bulabilirsiniz. [Not: Site örnek aradığım zamanda açıktı, fakat kapanmış. Google ön belleğinden bakabilirsiniz. Fakat resimler
yok tabi ki :( ]

Seviye-tetiklemeli kesmelerde ise paylaşım mümkündü. Burada da sürücü tarafında dikkat edilmesi gereken kısımlar var. Eğer sürücüde bir hata bulunuyorsa seviye tetiklemeli kesmeler makinenin kilitlenmesine sebebiyet verebilir. Birden fazla cihazın seviye-tetiklemeli olarak hattı paylaştığını varsayalım. Her cihazın kendi üstünde bir şekilde kesmenin beklediğini işaret eden bir de yazmaç bulunduğunu düşünelim. Herhangi bir cihaz kesme ürettiğinde, kesme işleyicisi çalıştı ve paylaşılan hat için kaydedilen her bir cihazın kesme işleyicisini ("zincir") tek tek çalıştırmaya başladı. Zincirdeki her cihazın kesme işleyicisi, cihazda
hakikaten bekleyen bir kesme var mı diye bakacak, var ise yapması gerekenleri yapacak, kesme üreten cihaz bulunduğu ve isteği cevaplandığı için de kesme işleyici zincirine devam edilmeyecek, çünkü cihaz bulundu. Geri dönüldüğünde eğer başka bir cihaz da bu zaman içerisinde kesme üretmiş ve hattı aktif tutmaya devam ediyorsa, kesmeler de seviye tetiklemeli olduğundan hemen tespit edilecek ve yeniden kesme işleyicisi zincirine dönülerek, tek tek cihaz bulunmaya çalışılacak. Ki bulunarak isteği cevaplansın ve kesme üretmesi durdurulsun. İsteği cevaplanana kadar hattı aktif tutacak.

Düşünün ki zincirdeki bir kesme işleyicisi bir hata sonucunda tamam bu kesme bana ait dedi ve ben bunu hallettim dedi. Halbuki kesmeyi üreten başka bir cihaz ve bu cihazın kesme işleyicisine de girilemedi. Geri dönüldüğü anda hat aktif olmaya devam edeceğinden sürekli işlemciye kesme gelecek. İşlemci de kesmelerden başka bir iş yapamaz hale gelecek. Makine bir döngüye girerek kilitlenecek.

Saturday, February 17, 2007

SMM, USB Legacy Desteği ve USB Handoff üzerine

x86 işlemcilerin protected, real ve virtual-8086 modlarının dışında çalıştığı özel bir mod daha bulunuyor: System Management Mode (SMM). Bu mod, güç yönetimi, sıcaklık ve fan takibi gibi sistem kontrolüne yönelik özel işler için ve yalnızca BIOS'un kullanımına yönelik olarak tasarlanmış bir mod. ACPI öncesi bahsi geçen işler, işlemcinin bu özelliği kullanılarak yapılıyor. Mekanizma şu şekilde işliyor: İşlemci'de aynı INTR# pin'i gibi bir de SMI# pin'i bulunuyor. Bu pin aktif olduğu anda işlemci ne yapıyorsa yapsın o işi bırakıyor, bekleyen tüm komutların sonuçlanmasını bekliyor, write-buffer'ını boşaltıyor, işlemci durumunu sistem hafızasından ayrı bir adres uzayı olan SMRAM'e yazıyor ve SM mod'a geçiş yapıyor. SMRAM içerisinde gerçekleşen durum ile başa çıkacak BIOS SMI handler kodu çalışıyor. İş bitince yalnızca bu modda çalışan RSM opkodu çalıştırılıp, moddan çıkılıyor. İşletim sisteminin ruhu duymadan, sanki hiç bir şey olmamış gibi herşey kaldığı yerden devam ediyor.

SMRAM, SM modunda kullanılan özel bir hafıza bölgesi. SMRAM sistem tasarımına bağlı olarak ne uygulamaların, ne de işletim sisteminin görebileceği bir yerde bulunuyor. BIOS, POST sonrası bu modda çalışacak SMI handler'ın kod, data ve stack bölümlerini daha önceden buraya kopyalamış oluyor. Geçiş sırasında da işlemci durumu daha sonra geri dönülebilmesi için SMRAM'in belirli bir bölgesinde saklanıyor. SMRAM sistem tasarımı açısından ayrı bir hafıza modülünde olabileceği gibi sistem hafızasının bir bölümü de SMRAM olarak kullanılabiliyor. Genelde de ikinci yöntem tercih ediliyor. Bu bölgeye işletim sistemi'nin dahi erişiminin engellenmesi işi chipset tasarımı ile sağlanıyor. Genelde uygulanan yöntem 640 KB ile 1 MB arasındaki 0xA0000 adresi ile başlayıp 0xBFFFF adresine haritalanmış olan Video RAM'i ile SMRAM'in sistem hafızasında aynı bölgeye haritalanması ile sağlanıyor. Herhangi bir modda 0xA0000 - 0xBFFFF fiziksel adresi arasına erişim istendiğinde chipset Video RAM'e ulaştırırken, SMI# sonrası SMM moda geçiş yapıldığında hafıza kontrolörü SMRAM'e erişim sağlıyor.

SMI kullanımına örnek olarak sistemde yerleştirilmiş olan bir sıcaklık sensörünün bağlı olduğu mikrodenetleyicinin belli bir sıcaklık seviyesine ulaştığında SMI (System Management Interrupt) ile işlemciyi uyarması ve ardından BIOS SMI handlerı çalışarak firmware seviyesinde gereken işi yapması olarak verilebilir. Kritik seviyede BIOS makineyi kapatabilir. Burada önemli nokta bu durumdan işletim sisteminin haberi olmaması. ACPI'de ise durum farklı, bu durumdan işletim sisteminin de haberi oluyor, ilgili mesajları gösterebiliyor ve ACPI işletim sistemi tarafında makine kapatılıyor.

SMI'ı kullanan daha ileri seviye bir yapı daha var. O da BIOS'lardaki USB Legacy Desteği özelliği.Bu ileri seviye özellik, USB mouse ve klavye kullanan makinelerin USB desteği olmayan işletim sistemlerinde PS/2 mouse/klavye emülasyonu yapılması için tasarlanmış. İşletim sistemi mouse ve klavye verilerini PS/2 veri yolu üzerinden aldığını zannediyor.

Tabi ki bu işin bir patenti var. Muhteşem google patents hizmeti sayesinde ayrıntılı olarak işleyişi görebiliriz. İşleyiş Open Host Controller Interface Speklerinin Appendix B kısmında "Legacy Support Interface Specification" başlığı altında anlatılıyor. Tabi ki patent bunun donanımsal olarak implimentasyonu üzerine.

İşleyiş OHCI host controller için şu şekilde:

Öncelikle standart olarak klavye ve mouse veri alışverişinin "keyboard controller (KBC)" üzerinden 0x60 ve 0x64 portları aracılığı ile gerçekleştiğini bilmemiz gerekiyor. Yine PC mimarisinde standart olarak klavye için IRQ1 ve mouse için IRQ12 interruptlarının kullanıldığını bilmeliyiz. Sisteminizde /proc/interrupts dosyasına baktığınızda bu iki interrupt'ın da i8042 tarafından kullanıldığını göreceksiniz. Şu adreste i8042 yazmaçları için güzel bir özet bulunuyor. Burada daha fazla detaylandırmak anlamsız. Çekirdekte düşük seviye i8042 sürücüsü ise drivers/input/serio/i8042.c altında bulunuyor.

Emülasyon modunda 0x60 ve 0x64 portlarına okuma ve yazmalar host controller üzerindeki yazmaçlara gerçekleşiyor. USB legacy desteği açık ise ve USB klavye ve ya mouse takılı ise host controller usb paketlerini yakaladığı anda işlemciye SMI gönderiyor, SM moduna giren işlemci'de BIOS SMI handler'da USB paketlerini yorumlayarak PS/2 datasına çeviriyor ve bu portlardan erişilecek şekilde host controller üzerinde ilgili yere yazıyor. Portlara herhangi bir yazma işlemi yapıldığında host controller yakalandığı için yine bir SMI göndererek BIOS'a girilip PS/2 data USB paketlerine çevrilip host controller'a iletiliyor. Host controllerda USB paketlerini cihazlara gönderiyor.

Buradaki ilginç nokta eğer USB emulasyonu açık ise ve sisteminizde hiç USB cihaz bağlı değil, yalnızca PS/2 klavye ve mouse kullanıyor olsanız bile herşey yine host controller emulasyonu ile gerçekleşiyor. Bunun için asıl KBC arayüzünün oluşturduğu interruptlar IRQ1 ve IRQ12 host controller üzerinden geçiriliyor. Yani asıl interrupt controller ile interrupt üreticisi arasında bulunuyor. Yakaladığı anda biliyor ki KBC de PS/2 datası bulunuyor. Bunları alıp emülasyonda asıl kendi üzerinden erişilen portların bulunduğu bölgeye ilgili PS/2 verilerini yerleştiriyor ve ardından ilgili interrupt'ı gönderiyor. İşletim sistemi interruptı yakalandıktan sonra verileri host controller'ın legacy portları (0x60 ve 0x64) üzerinden okunuyor. Biraz saçma gibi gelebilir. Ortada USB filan yokken neden böyle bir şeye gerek var. Sebebi USB'nin de olabileceği. Yani USB mouse ile PS/2 klavye'yi birlikte kullanabilmelisiniz. Bu yüzden emülasyon modundan herşey host controller üzerinden geçiyor. Bu emulasyon yapısı'nın OHCI için geçerli olduğunu belirtelim, UHCI ile benzer ancak ufak farklar içeren bir şekilde emulasyon işi gerçekleşiyor. Tüm host controller'ların speklerinde ilgili bölümlerde ayrıntılı bilgi bulunuyor.

USB mouse ve klavye paketleri host controller'a geldikten sonra işlemciye SMI gönderiyor, böylece emulasyon yapılıyor dedik. Emulasyonun amacı USB desteği olmayan ancak PS/2 desteği olan işletim sistemleri içindi. O zaman USB desteği olan işletim sistemlerinde host controller sürücüsü'nün bir noktada USB interruptlarını karşılıyor olması gerekiyor. İşte bu işleme BIOS'un işletim sistemine USB devri ("USB Handoff") ismi veriliyor. OpenHCI için konuşacak olursak, host controller basit bir şekilde HcControl yazmacında InterruptRouting bitini 1 ve ya 0 yaparak sırasıyla interruptları SMI aracılığı ile işlemciye ya da sistem interrupt kontrolörüne iletmeye başlıyor. Sistem interrupt kontrolörü işlemeye başladıktan sonra artık BIOS değil işletim sistemi USB paketlerini işliyor. InterruptRouting biti yalnızca okunabilir bir bit. Bu biti değiştirmek için önce HcInterruptStatus yazmacında OC (OwnershipChange) bitini set etmek daha sonra da HcControlStatus yazmacının OCR (OwnershipChangeRequest) bitini set etmek gerekiyor. Eğer InterruptRouting biti 0 olmuş ise artık interruptlar sistem kontrolörüne gidecek ve işletim sistemi yönetecek demektir. Handoff bir sebeple gerçekleşmez ise USB hiç bir cihaz çalışmayacak. InterruptRouting bitinin 0 olmadığı sürece host controller tüm interruptlerı SMI'a route edecek. Çekirdeğe baktığımızda bu işin şu şekilde yapıldığını görüyoruz:


[drivers/usb/host/ohci-hcd.c]

#ifndef IR_DISABLE
/* SMM owns the HC? not for long! */
if (!no_handshake && ohci_readl (ohci,
&ohci->regs->control) & OHCI_CTRL_IR) {
u32 temp;

ohci_dbg (ohci, "USB HC TakeOver from BIOS/SMM\n");

/* this timeout is arbitrary. we make it long, so systems
* depending on usb keyboards may be usable even if the
* BIOS/SMM code seems pretty broken.
*/
temp = 500; /* arbitrary: five seconds */

ohci_writel (ohci, OHCI_INTR_OC, &ohci->regs->intrenable);
ohci_writel (ohci, OHCI_OCR, &ohci->regs->cmdstatus);
while (ohci_readl (ohci, &ohci->regs->control) & OHCI_CTRL_IR) {
msleep (10);
if (--temp == 0) {
ohci_err (ohci, "USB HC takeover failed!"
" (BIOS/SMM bug)\n");
return -EBUSY;
}
}
ohci_usb_reset (ohci);
}
#endif


SMM özel bir mod. BIOS'daki herhangi bir hata ile makine hard freeze olabilir. Documentation/i386/usb-legacy-support.txt eski bir belge ama spesifik makinelerde legacy usb ile ilgili karşılaşılan bazı sorunlar dokümante edilmiş. Daha fazla örnek olsa güzel olurmuş.

Burada da Windows 98 işletim sistemine özgü olduğu belirtilmiş bir race'in yol açtığı sorundan bahsediliyor. PCI enumeration sırasında işletim sistemleri, cihazların "configuration space"'inde bulunan BAR'lara belli bir pattern yazarak, daha önce BIOS'un haritaladığı adres'in ne uzunlukta olduğu bilgisini alıyor. /proc/iomem altında görünen başlangıç adresi BAR'dan alınırken, boyut da aynı şekilde bu pattern yazılarak alınıyor ve eklenerek bittiği adres bulunuyor. Windows 98'de karşılaşılan race durumunda ise işte tam bu esnada SMI gelir ve BIOS'a girilirse normalde controller yazmaçlarına eriştiği adres olan BAR'daki adres hatalı olacak ve bu da SMI#'ın sürekli aktif olması ile sonuçlanacak denilmiş. Yanlış isem düzeltiniz: SMI kaynağı, chipsetlerde belli bitlerin aktif hale gelmesi ile anlaşılıyor. Chipset aktif bit gördüğü anda SMI üretiyor. [Güncelleme: Intel 82801AA (ICH) datasheetinden bir örnek verirsek: 5.12.6.1 de THRM# sinyali aktif olduğu anda THRM_STS biti 1 yapılıyor. Eğer THRM_EN biti de aktif ise ikisinin aktif olduğu an SMI üretiliyor.] SMM'de işini bitirince bu biti temizliyor. Temizlemeden handler'dan çıkılırsa sürekli SMI üretilip duruluyor çünkü bit aktif ve sanki yeni bir SMI'mış gibi algılanıyor. Bundan sonra OHCI spekinin 5.1.13.3 bölümünde anlatıldığı üzere OS OwnershipChangeRequest bitini set ettiği anda SMM bundan haberdar olup temizlik işlerini yapıp InterruptRouting bitini temizliyor. Böylece handoff gerçekleşiyor. Ancak adresteki raporda belirtildiğine göre SMI'ın sürekli aktif olması sebebiyle bu request SMM tarafından karşılanmıyor ve handoff gerçekleşmiyor. Bu da Windows 98 makinede USB cihazların algılanamaası ile sonuçlanıyor.

Thursday, February 15, 2007

PCI Latency Timer, MAX_LAT ve MIN_GNT

PCI cihaz bus'a erişmek istediği anda REQ# hattı üzerinden arbiter'a istekte bulunuyor. Arbiter'da PCI speklerinde implimentasyona bırakılan adil bir şekilde cihazların isteklerini GNT# hattını aktif hale getirerek karşılıyor. Eğer GNT# aktif hale getirildikten 16 PCI clock sonra bir işlem başlamaz ise, arbiter, cihazı "kırık" olarak kabul edip sonraki istekleri gözardı edebilir.

Cihaz GNT# aldıktan sonra adres fazı sonrası, data fazına başlayacak. Cihaz izni alana kadar diğer cihazların devam eden işlemlerinin bitmesini bekledi, arbiter'ın kendisine bus'ı vermesini bekledi. Eğer bu kadar zaman bekledikten sonra, bus'ı alıp da, DWORD boyutunda tek bir veri transfer işlemi yapacak olsa cihaz açısından performans oldukça düşük olacaktır. Bunun için veri transferi "burst" modda gerçekleştiriliyor. Başlayan transferin veri fazı ardından ardışıl adreslere veri transferi devam ediyor.

Peki nereye kadar? Bunu belirlemek için PCI "bus-mastering" özelliğine sahip ve iki data fazından fazla burst edebilecek cihazların bir zamanlayıcı barındırması gerekiyor. Bunun ismi "Latency Timer". Timer'ın sayacağı değer ise cihazın "configuration space"'inde saklı. Her transfer başlangıcında (FRAME# aktif hale getirildiğinde) timer yeniden başlıyor. Timer sona erdiğinde cihaz işlemi sonlandırıyor. (Aslında zamanlayıcı sonlandırma için tek parametre değil. O an yürütülen PCI işlem cinsine göre de gelebilen bir takım parametreler daha var. Ayrıntılar spekte ve yazılımsal açıdan baktığımızda çok da önemli değil.) Daha önce de işini bitirebilir ama timer dolduğunda bus'ı bırakmak zorunda.

Genel olarak baktığımızda burada ortaya çıkan "Latency Timer"'ın cihazın ne kadar bus'a sahip olacağı üzerinde önemli bir etkisinin olması. Eğer bu değer çok kısa olursa, diğer bekleyen cihazların gecikmesi azalıyor, eğer fazla olursa daha fazla veri gönderimi yapılıp, cihaz açısından performans artışı oluyor. "Latency" ve "Throughput" dengesi söz konusu.

Peki bu değeri kim ve neye göre belirliyor? Değeri asıl belirleyen BIOS. Açılış sırasında bu değeri "configuration space"'de tüm cihazların ilgili offsetlerine yazıyor. Bunun dışında nadiren çekirdek sürücüleri de tanıdığı cihaz için bu değer ile oynayarak bir nevi "fine tuning" yapabiliyor. Değeri root hakları ile kullanıcı da "setpci" komutu aracılığı ile yazabilir. Çok nadir durumlarda ses kartı ile ilgili ve ya görüntü yakalama kartları ile ilgili gecikme problemlerini çözmek için kullanılabilir. Ama değerle oynamak çoğu zaman gereksiz. "Latency timer" register'ı "configuration space" 0Dh offsetinde bulunuyor.

faik@iago ~ $ /usr/sbin/lspci -s 0a:08.0 -xxx
0a:08.0 Ethernet controller: Intel Corporation PRO/100 VE Network Connection (rev 02)
00: 86 80 92 10 07 00 90 02 02 00 00 02 10 40 00 00
10: 00 50 00 d2 01 60 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 4d 10 ef 81
30: 00 00 00 00 dc 00 00 00 00 00 00 00 0b 01 08 38

faik@iago ~ $ sudo /usr/sbin/setpci -s 0a:08.0 latency_timer=80

faik@iago ~ $ /usr/sbin/lspci -s 0a:08.0 -xxx
0a:08.0 Ethernet controller: Intel Corporation PRO/100 VE Network Connection (rev 02)
00: 86 80 92 10 07 00 90 02 02 00 00 02 10 80 00 00
10: 00 50 00 d2 01 60 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 4d 10 ef 81
30: 00 00 00 00 dc 00 00 00 00 00 00 00 0b 01 08 38

Bu değer cihazın ihtiyaçlarına göre belirlenmesi gereken bir değer. Cihazın ihtiyaçlarını da BIOS'un ya da çekirdeğin anlayabilmesi için cihaz'ın "configuration space"'inde tanımlı iki adet register mevcut. Bunlar MAX_LAT ve MIN_GNT registerları. Sırasıyla 3Fh ve 3Eh offsetlerinde bulunuyorlar.

MIN_GNT bir cihazın ne uzunlukta "burst" zamanına ihtiyacı olduğunu 33 Mhz'lik PCI clock referansı ile 8 PCI clock birim cinsinden saklıyor. Sebebi aşağıdaki örnek ile daha rahat anlaşılıyor. MAX_LAT ise cihazın ne sıklıkta bus'a ihtiyacı olduğunu belirtiyor.

PCI speklerinde çok güzel bir örnek verilmiş. 100 Mbs'lik bir ethernet cihazı yaptığınızı düşünün. Yaklaşık 10 MB/s lik bir "throughput" karşılamak istiyorsunuz. Ethernet'inizin de her iki yön için 64-byte boyutunda bufferları bulunuyor. Her bir buffer'ı iki 32-bytelık bölme olarak (ping-pong buffer) yapmak optimum performansı sağlıyor denilmiş. Rx buffer'ı düşünün. 32-byte yazılan diğer 32-byte'lık bölmeye kaydı. O işlenirken aynı zamanda önceki 32-byte'da doldurulabiliyor. Her 32-byte transfer 8 DWORD'e tekabul ediyor. Bu da 8 veri fazı/8 clock demek. 33 Mhz için 1/4 mikro saniyelik (1/33 Mhz * 8) bir zamana denk geliyor. Birim olarak en az 8 clock alınmış. Yani MIN_GNT 1 olmalı. Eğer böyle olursa cihaz istemiş olduğu 32-bytelık "burst" transferi bu zaman içinde bir seferde gerçekleştirebilir. 10 MB/s lik "throughput" için ise 32-byte'lık buffer için 32/10 MB/s ile 3.2 mikro saniyede bir "burst" yapmak yeterli geliyor. Bu da yaklaşık MAX_LAT için 12 değerine denk geliyor. (3.2 / 0.25) MAX_LAT ve MIN_GNT yazmaçları sadece okunabiliyor. Cihaz karakterini belirliyor.

Çekirdek açılışında görünen şu mesajlar, çekirdeğin ayarladığı değeri bildiriyor:

PCI: Setting latency timer of device 0000:00:1d.1 to 64

[arch/i386/pci/i386.c]


/*
* If we set up a device for bus mastering, we need to check the latency
* timer as certain crappy BIOSes forget to set it properly.
*/
unsigned int pcibios_max_latency = 255;

void pcibios_set_master(struct pci_dev *dev)
{
u8 lat;
pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat);
if (lat < 16)
lat = (64 <= pcibios_max_latency) ? 64 : pcibios_max_latency;
else if (lat > pcibios_max_latency)
lat = pcibios_max_latency;
else
return;
printk(KERN_DEBUG "PCI: Setting latency timer of device %s to %d\n",
pci_name(dev), lat);
pci_write_config_byte(dev, PCI_LATENCY_TIMER, lat);
}

Çekirdeğin ayarlamasında anladığım iki sebep var. Birincisi comment'de belirtildiği gibi bazı BIOS'ların yanlış set etmesi ve ya hiç etmemesi. İkinci sebebi ise bazı chipsetlerdeki sorunlara workaround yapabilmek için. Örneğin bazı SiS chipsetlerinde latency timer 32'den büyük olursa bus'da kilitlenme olabiliyormuş.

[arch/i386/pci/fixup.c]

static void __devinit pci_fixup_latency(struct pci_dev *d)
{
/*
* SiS 5597 and 5598 chipsets require latency timer set to
* at most 32 to avoid lockups.
*/
DBG("PCI: Setting max latency to 32\n");
pcibios_max_latency = 32;
}
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_5597, pci_fixup_latency);
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_5598, pci_fixup_latency);

BIOS tarafında özel bir şey var mı diye LinuxBIOS'a baktığımda öntanımlı olarak tüm timerları 0x40 olarak ayarladığını görüyorum. Hani tüm cihazlara bakılıp bir algoritmayla optimum paylaşım için değerler hesaplayıp ayrı ayrı yazma gibi bir şey yok.

[src/devices/pci_device.c]

void pci_dev_set_resources(struct device *dev)
...
/* set a default latency timer */
pci_write_config8(dev, PCI_LATENCY_TIMER, 0x40);

Daha inceleme fırsatı bulamadığım PCI Express mimarisinde MIN_GNT ve MAX_LAT bir şey ifade etmiyor. PCI Express yapısı PCI'ya göre oldukça farklı. İnceleyince daha ayrıntılı görürüz.

Wednesday, February 14, 2007

PCI veri güvenilirliği üzerine

x86 mimarisine sahip makinelerde işlemcinin anladığı iki adres uzayı bulunuyor. Hafıza ve I/O. x86, I/O adresindeki bir PCI cihaza yazma işlemi yapıldığında işlemci devam etmeden, yazma işleminin cihaza eriştiğinden emin olabiliriz. Fakat MMIO adresindeki bir cihaza yazma işlemi yapıldığında işlemci devam ederken, yazmanın cihaza eriştiğinden emin olamayız. PCI cihaza yapılan yazma işlemi bir yerlerde (host bridge'de ya da ikinci bir PCI bus'a giden bir ara bridge'de) bekletiliyor olabilir. Toplu yazma yapılabileceği durumlar için bir tasarruf. Bu işleme "write-posting" deniyor. Eğer işlemcinin bir sonraki yapacağı işlemde cihaza önceki yazma işleminin sonuçlanmış olması gerekliliği var ise, cihaz üzerinde herhangi "etkisiz" bir yerden okuma yapılabilir. Tüm bridge'ler okuma işlemi öncesi bekletilen yazma işlemlerini tamamlıyor. Bir nevi, buffer "flush" ediliyor.

Bunlar sürücü açısından baktığımız zaman bildiğimiz şeyler. Ama ilginç olan bir şey var ki, cihaz açısından baktığımızda garip bir durum ortaya çıkıyor. Herhangi bir cihaz, hafızada bir yere yazma işlemini bitirdikten sonra işlemciye bir interrupt göndererek bunun haberini verdiğini düşünelim. İşte burada öyle bir durum olabilir ki interrupt işlemciye, veriler hafızaya ulaşmadan erişebilir. İlginç... Bunu engellemek için PCI speklerinde üç yöntemden bahsediliyor:

1. Sistem donanımı "write-posting" yapılan bufferları, cihaz'ın interruptı işlemciye ulaşmadan önce flush edebilir. Yani donanımsal olarak yazmalar izlenerek interrupt görüldüğünde yazmaların ulaştığını donanım garantileyecek.
2. Cihaz yazma işleminin hemen ardından yazdığı veriyi okur ve ardından interrupt gönderir.
3. Sürücü interrupt geldikten sonra cihazın üzerindeki bir yazmacı okur ve ardından cihazın yazdığı veriye erişir.