PCI-e öğrenimi: Sürücü ve DMA
(blog.davidv.dev)- Hardcoded BAR0 adresine doğrudan peek/poke yapma aşamasından çıkıp, Linux PCI alt sistemi ile BAR belleğini buluyor ve çekirdek sürücüsü aygıtı başlatıyor
- Sürücü,
struct pci_driveriçindeki ID tablosu veprobefonksiyonuyla başlar; BAR0'ı çekirdek sanal adresine eşledikten sonra kullanıcı alanı erişimini hazırlar /dev/gpu-iokarakter aygıtı üzerindenread(2)vewrite(2)bağlanır;container_ofile dosya işlemlerinden sürücü durumu geri alınır- DWORD birimli kopyalama, 1.2MiB aktarım için yaklaşık 800ms sürdü; ancak MMIO register tabanlı DMA çağrısına geçince yaklaşık 300µs seviyesine düştü
- DMA tamamlanmasını bekleme MSI-X interrupt ve wait queue ile işlenir; sonunda QEMU konsolunda framebuffer içeriğini gösteren sahte bir GPU gibi çalışır
BAR0'ı çekirdek sürücüsünde bulmak ve eşlemek
- Önceki uygulama,
lspciden kopyalanan BAR0 adresi0xfe000000üzerinde 32 bitlik birimlerle doğrudan okuma ve yazma yapıyordu - Adresi hardcode etmemek için Linux PCI alt sisteminden aygıtın bellek eşleme bilgileri alınır
struct pci_driveriçin iki temel alan gerekir- Desteklenen device/vendor ID çiftlerinin tablosu
- ID eşleştiğinde çağrılan
probefonksiyonu
- Örnek aygıt
PCI_DEVICE(0x1234, 0x1337)ile eşleşir - Sürücü durumu
GpuState,struct pci_dev *pdevve BAR belleği içinu8 __iomem * hwmemsaklar probefonksiyonu aygıtı şu sırayla hazırlarpci_enable_device_mem(pdev)ile aygıt belleğine erişimi etkinleştirirpci_select_bars(pdev, IORESOURCE_MEM)ile kullanılabilir bellek BAR bit alanını alırpci_request_region(pdev, bars, "gpu-pci")ile BAR adres alanının sahipliğini isterpci_resource_start(pdev, 0)vepci_resource_len(pdev, 0)ile BAR0'ın başlangıç adresini ve uzunluğunu alırioremap(mmio_start, mmio_len)ile fiziksel adresi çekirdek sanal adresine eşler
module_initiçindepci_register_driverçağrıldığında boot log'undammio starts at 0xfe000000ve çekirdek sanal adresi yazdırılır
Kullanıcı alanına karakter aygıtı olarak sunmak
- BAR0 adres alanı çekirdek sürücüsüne eşlendikten sonra, kullanıcı alanı programının
read(2)vewrite(2)ile PCIe aygıtıyla etkileşmesi için bir karakter aygıtı oluşturulur - Bu sürücüde yalnızca üç dosya işlemi gerekir:
open,read,write GpuStateestruct cdev cdeveklenir vesetup_chardeviçinde şu işlemler yapılıralloc_chrdev_regionile aygıt numarası ayrılırcdev_initvecdev_addile karakter aygıtı kaydedilirdevice_createile/dev/gpu-iooluşturulur
/dev/sözde dosya sistemini doldurmak için init script'ine/busybox mdev -seklenir- Bundan sonra
/dev/gpu-iobir karakter aygıtı olarak görünür; örnekte major numarası241, minor numarası0olarak gösterilir
container_of ile dosya işlemlerinden sürücü durumunu bulmak
writeuygulamasındastruct file*içindekiprivate_dataalanınıopendoldurmalıdır; ancakopenayrı birprivate_dataya dauser_dataargümanı almazstruct inodeiçinde karakter aygıtını gösterenstruct cdev *i_cdevişaretçisi bulunurGpuState,struct cdevi içine gömdüğü içincontainer_of(inode->i_cdev, struct GpuState, cdev)ileGpuStateişaretçisi geri alınabilirgpu_open, elde edilenGpuStateifile->private_dataiçine kaydeder- Daha sonra
gpu_readvegpu_write,file->private_dataiçindenGpuStatei çıkarıp kullanır - İlk
read/writebir seferde bir DWORD işlergpu_read,ioread32(gpu->hwmem + *offset)ile okur vecopy_to_userile kullanıcı tamponuna kopyalargpu_write, kullanıcı tamponundan 4 bayt kopyalar ve offset'i 4 artırır
- Küçük aktarımlarda çalışır; ancak CPU sürekli tek tek paket işlemek zorunda kaldığı için büyük aktarımlarda yavaştır
- 640×480, 32bpp'ye karşılık gelen 1.2MiB aktarım yaklaşık
800mssürer
MMIO register'larıyla DMA çağrısı oluşturmak
- CPU'nun DWORD birimli kopyalamayı tekrar etmesi yerine, veriyi aygıtın doğrudan kopyalaması için DMA kullanılır
- İş isteği memory-mapped IO yöntemiyle gönderilir
- Bazı bellek adresleri DMA çağrısının argümanları gibi davranan register'lar olarak kullanılır
- Diğer adresler ise fonksiyon çağrısını çalıştırmak anlamına gelen komutlar gibi kullanılır
- DMA arayüzünde CPU'nun aygıta bildirmesi gereken değerler vardır
- Kopyalanacak verinin source adresi ve uzunluğu
- destination adresi
- Veri yönü: main memory tarafına veya main memory'den
- Kopyalamayı başlatmaya hazır olunduğu sinyali
- Aygıt aktarımın tamamlandığını CPU'ya bildirmelidir
- Örnek register'lar şöyle tanımlanır
REG_DMA_DIRREG_DMA_ADDR_SRCREG_DMA_ADDR_DSTREG_DMA_LEN
CMD_DMA_START, register değerlerini doldurma işlemiyle gerçek DMA başlatmayı ayıran komut adresi olarak kullanılır- Çekirdek sürücüsünün
execute_dmafonksiyonu,iowrite32ile yönü, source'u, destination'ı ve uzunluğu yazar; en sondaCMD_DMA_STARTadresine1yazar
QEMU aygıtı tarafında DMA işleme
- QEMU adaptörünün MMIO
gpu_writefonksiyonu önceki uygulamanın yerini alarak DMA register'larını ve komutları işler - Register alanına yazılanlar
gpu->registers[reg]içine değer olarak kaydedilir - Komut alanında
REG_DMA_STARTgeldiğinde DMA yönü kontrol edilir DIR_HOST_TO_GPUyönündepci_dma_readçağrılır- host adresi
REG_DMA_ADDR_SRCdir - device adresi
gpu->framebuffer + REG_DMA_ADDR_DSTdir - uzunluk
REG_DMA_LENdir
- host adresi
- Diğer DMA yönleri örnek kodda
Unimplemented DMA directionolarak işlenir - Çekirdek sürücüsünün
gpu_fb_writefonksiyonu kullanıcı verisini DMA'ya şu adımlarla aktarırkmalloc(count, GFP_KERNEL)ile çekirdek tamponu ayırırcopy_from_userile kullanıcı verisini çekirdek tamponuna kopyalardma_map_single(&gpu->pdev->dev, kbuf, count, DMA_TO_DEVICE)ile DMA adresi oluştururexecute_dma(gpu, DIR_HOST_TO_GPU, dma_addr, *offset, count)çağırırkfree(kbuf)ile tamponu serbest bırakır
- Bu yöntem örnek sistemde yaklaşık 300µs ölçülecek kadar hızlanır
DMA tamamlanmasını MSI-X interrupt ile bildirmek
- DMA yürütmesi asenkron olduğundan,
writeın tamamlanana kadar bloklanmasını sağlamak daha kullanışlıdır - PCI-e kartı CPU'ya Message Signalled Interrupts ile sinyal gönderebilir
- MSI, özel elektriksel bağlantı kullanan klasik interrupt'lardan farklı olarak interrupt'ı veri yolu üzerindeki normal mesaj paketleriyle iletir
- MSI-X ayarı için QEMU aygıtında iki alan bulunur
- Her interrupt ayarını saklayan MSI-X table
- pending interrupt bitmap'i olan PBA
- Örnek sabitler şöyledir
IRQ_COUNTdeğeri1dirIRQ_DMA_DONE_NRdeğeri0dırMSIX_ADDR_BASEdeğeri0x1000dirPBA_ADDR_BASEdeğeri0x3000dir
- QEMU'nun
pci_gpu_realizefonksiyonundamsix_initvemsix_vector_useçağrılarak MSI-X başlatılır lspci -vvçıktısında MSI-X etkin görünür; vector table BAR0 offset00001000, PBA ise BAR0 offset00003000olarak gösterilirpci_dma_readbittikten sonramsix_notify(&gpu->pdev, IRQ_DMA_DONE_NR)çağrılarak interrupt gönderilir
Çekirdek IRQ handler'ı ve bus mastering
- Çekirdek sürücüsü
pci_alloc_irq_vectorsile MSI-X/MSI vektörlerini ayırır vepci_irq_vectorile IRQ numarasını alır request_threaded_irqileGPU-Dma0handler'ı kaydedilir- Boot sonrasında
/proc/interruptsiçinde örnekteki gibi IRQ24,PCI-MSIX-0000:00:02.0veGPU-Dma0olarak gösterilir - Başta çalışmaz; çünkü kartın CPU'dan bağımsız olarak mesaj gönderme yetkisi yoktur
- Aygıtın CPU müdahalesi olmadan sistem belleğini doğrudan işleyebilmesini sağlayan özellik bus masteringdir
- Çekirdekteki
gpu_probeiçindepci_set_master(pdev)çağrılırsa aygıta bus master yetkisi verilir - Bundan sonra
writeiki kez çağrıldığında çekirdek log'undaIRQ 24 receivediki kez yazdırılır
wait queue ile gerçek blocking write uygulamak
- Interrupt tabanlı bildirim hazır olduğunda Linux wait queue ile
writebloklayan bir çağrıya dönüştürülebilir - Global durum olarak
wait_queue_head_t wqvevolatile int irq_fired = 0tutulur - IRQ handler'ı şu işleri yapar
irq_fired = 1ile tamamlanma durumunu ayarlarwake_up_interruptible(&wq)ile bekleyen thread'i uyandırırIRQ_HANDLEDdöndürür
setup_msiiçineinit_waitqueue_head(&wq)eklenirgpu_fb_write, DMA yürütüldükten sonrawait_event_interruptible(wq, irq_fired != 0)ile interrupt'ı bekler- Bekleme sırasında interrupt edilirse
-ERESTARTSYSdöndürür
QEMU konsolunda framebuffer göstermek
- Kullanıcı alanındaki
write(2)çağrısını alıp DMA ile PCI-e aygıtına aktaran bir framebuffer oluştuğu için, QEMU konsol çıktısına bağlanarak çalışan bir GPU gibi görünmesi sağlanır - QEMU'nun
GpuStateineQemuConsole* coneklenir pci_gpu_realizeiçindegraphic_console_initile konsol oluşturulur veqemu_console_surfaceile display surface alınır- İlk test pattern'i 640×480 aralığındaki surface verisine değerler doldurularak gösterilir
vga_update_display,gpu->framebufferiçeriğini QEMU display surface'e kopyalardpy_gfx_update(gpu->con, 0, 0, 640, 480)ile 640×480 alanı güncellenir- Daha sonra underlying device'a pattern yazıldığında görüntü değişir
- Kaynak kod the Github repo içinde bulunur
1 yorum
Hacker News yorumları
Başlamak için Tang Mega 138k [0] aldım, ancak dokümantasyonu fazla olmadığı için zaman alıyor.
PCI-e hard IP bulunan uygun fiyatlı FPGA kartı önerisi olan varsa duymak isterim.
[0]: https://wiki.sipeed.com/hardware/en/tang/tang-mega-138k/mega...
Spartan 6 https://www.blackmagicdesign.com/products/decklink/techspecs...
Artix https://www.blackmagicdesign.com/products/decklink/techspecs...
Artix https://www.blackmagicdesign.com/products/decklink/techspecs...
Artix https://www.blackmagicdesign.com/products/decklink/techspecs...
Ancak dış yüksek hızlı arayüz olarak yalnızca bir USB 3.1 Gen 1 var.
https://shop.lambdaconcept.com/home/50-screamer-pcie-squirre...
Litefury, “NVMe SSD” form faktöründe (2280 Key M) bir Xilinx Artix FPGA kiti; Xilinx XC7A100T kullanıyor ve 102 euro.
Dış yüksek hızlı LVDS giriş/çıkışları ise yalnızca birkaç tane.
https://rhsresearch.com/collections/rhs-public/products/lite...
Vivado, profesyonel yazılım mühendisi ölçütleriyle “mükemmel” bir araç olmasa da FPGA geliştirme ve uygulama tarafında kesinlikle sektörün en iyi seviyesinde.
Xilinx’in PCIe aygıt geliştirme yolu da oldukça iyi oturmuş durumda.
Linux aygıt sürücüleriyle bizzat hiç uğraşmadım, ancak birkaç yıl önce başka bir işletim sisteminde birkaç PCIe sürücüsü üzerinde çalışmıştım ve kavramlar çok tanıdık geliyor.
Bu tür içeriklerin daha fazla olmasını isterim.
Ana fikri gösterecek kadar kod koyup adım adım üzerine inşa etmesi güzel.
Hayatım boyunca yeni bir PCI aygıtı yapmak istememiştim, ama şimdi biraz yapmak istiyorum; iyi teknik yazının asıl turnusol testi de bu değil mi?
Projem için bir geliştirme ve playtest ortamı kurmak istiyordum ama ne aratacağımı bile bilmiyordum; tam ihtiyacım olan içerikti.
Diğer 2 bölüm de iyiydi; boot services sürücü kodunu exit sonrasında kullanma yöntemi, bus mastering, MSI-X gibi pratik konular ve küçük ama yararlı birçok ayrıntı var.