PCI-e सीख: ड्राइवर और DMA
(blog.davidv.dev)- हार्डकोड किए गए BAR0 address पर सीधे peek/poke करने वाले चरण से आगे बढ़कर, Linux PCI subsystem के जरिए BAR memory खोजी जाती है और kernel driver device को initialize करता है
- ड्राइवर
struct pci_driverकी ID table औरprobefunction से शुरू होता है, BAR0 को kernel virtual address में map करने के बाद user space access के लिए तैयारी करता है /dev/gpu-iocharacter device के जरिएread(2)औरwrite(2)को जोड़ा जाता है, औरcontainer_ofसे file operations में driver state वापस प्राप्त की जाती है- DWORD unit copy में 1.2MiB transfer के लिए लगभग 800ms लगे, लेकिन MMIO register-आधारित DMA call पर बदलने से यह लगभग 300µs तक घट गया
- DMA completion का इंतज़ार MSI-X interrupt और wait queue से किया जाता है, और अंत में यह QEMU console में framebuffer contents दिखाने वाले fake GPU की तरह काम करता है
Kernel driver में BAR0 ढूंढना और map करना
- पिछला implementation
lspciसे कॉपी किए गए BAR0 address0xfe000000पर 32-bit unit में सीधे read और write करता था - address को हार्डकोड न करने के लिए Linux PCI subsystem से device की memory mapping जानकारी ली जाती है
struct pci_driverमें दो मुख्य fields चाहिए- supported device/vendor ID pairs की table
- ID match होने पर call किया जाने वाला
probefunction
- example device
PCI_DEVICE(0x1234, 0x1337)से match होता है - driver state
GpuStateमेंstruct pci_dev *pdevऔर BAR memory के लिएu8 __iomem * hwmemstore किया जाता है probefunction निम्न क्रम में device तैयार करता हैpci_enable_device_mem(pdev)से device memory access enable करनाpci_select_bars(pdev, IORESOURCE_MEM)से usable memory BAR bitfield प्राप्त करनाpci_request_region(pdev, bars, "gpu-pci")से BAR address space ownership request करनाpci_resource_start(pdev, 0)औरpci_resource_len(pdev, 0)से BAR0 का start address और length लेनाioremap(mmio_start, mmio_len)से physical address को kernel virtual address में map करना
module_initमेंpci_register_drivercall करने पर boot log मेंmmio starts at 0xfe000000और kernel virtual address print होता है
User space में character device के रूप में expose करना
- BAR0 address space को kernel driver में map करने के बाद, user space program को
read(2)औरwrite(2)से PCIe device के साथ interact करने देने के लिए character device बनाया जाता है - इस driver को केवल
open,read,writeतीन file operations चाहिए GpuStateमेंstruct cdev cdevजोड़करsetup_chardevमें ये काम किए जाते हैंalloc_chrdev_regionसे device number allocate करनाcdev_initऔरcdev_addसे character device register करनाdevice_createसे/dev/gpu-ioबनाना
- init script में
/busybox mdev -sजोड़कर/dev/pseudo filesystem को populate किया जाता है - इसके बाद
/dev/gpu-iocharacter device के रूप में दिखता है, और उदाहरण में major number241, minor number0दिखाई देता है
container_of से file operations में driver state ढूंढना
writeimplementation मेंstruct file*काprivate_dataopenको भरना होता है, लेकिनopenको अलगprivate_dataयाuser_dataargument नहीं मिलताstruct inodeमें character device को point करने वालाstruct cdev *i_cdevpointer होता है- क्योंकि
GpuStateमेंstruct cdevembed किया गया है,container_of(inode->i_cdev, struct GpuState, cdev)सेGpuStatepointer वापस पाया जा सकता है gpu_openप्राप्तGpuStateकोfile->private_dataमें store करता है- इसके बाद
gpu_readऔरgpu_write,file->private_dataसेGpuStateनिकालकर इस्तेमाल करते हैं - शुरुआती
read/writeएक बार में एक DWORD handle करते हैंgpu_readioread32(gpu->hwmem + *offset)से पढ़ता है औरcopy_to_userसे user buffer में copy करता हैgpu_writeuser buffer से 4 bytes copy करता है और offset को 4 बढ़ाता है
- छोटे transfer के लिए यह काम करता है, लेकिन बड़े transfer में CPU को हर packet अलग-अलग process करना पड़ता है, इसलिए यह धीमा है
- 640×480, 32bpp के बराबर 1.2MiB transfer में लगभग
800msलगे
MMIO registers से DMA call बनाना
- CPU द्वारा DWORD unit copy बार-बार करने के बजाय, device को सीधे data copy करने देने के लिए DMA का उपयोग किया जाता है
- work request memory-mapped IO के जरिए भेजा जाता है
- कुछ memory addresses ऐसे registers की तरह काम करते हैं जो DMA call के arguments बनते हैं
- दूसरे addresses ऐसे command की तरह काम करते हैं जो function call execute होने का मतलब देते हैं
- DMA interface में कुछ values हैं जो CPU को device को बतानी होती हैं
- copy किए जाने वाले data का source address और length
- destination address
- data direction: main memory की ओर या main memory से
- copy शुरू करने की readiness signal
- device को CPU को transfer complete होने की सूचना देनी होती है
- example registers इस तरह define किए गए हैं
REG_DMA_DIRREG_DMA_ADDR_SRCREG_DMA_ADDR_DSTREG_DMA_LEN
CMD_DMA_STARTको register values भरने की action और actual DMA start को अलग करने वाले command address के रूप में इस्तेमाल किया जाता है- kernel driver का
execute_dmaiowrite32से direction, source, destination, length लिखता है और अंत मेंCMD_DMA_STARTपर1लिखता है
QEMU device side पर DMA handling
- QEMU adapter का MMIO
gpu_writeपुराने implementation को replace करके DMA registers और commands को handle करता है - register region पर write होने पर value
gpu->registers[reg]में store की जाती है - command region में
REG_DMA_STARTआने पर DMA direction check की जाती है DIR_HOST_TO_GPUdirection मेंpci_dma_readcall किया जाता है- host address
REG_DMA_ADDR_SRCहोता है - device address
gpu->framebuffer + REG_DMA_ADDR_DSTहोता है - length
REG_DMA_LENहोती है
- host address
- दूसरी DMA direction को example code में
Unimplemented DMA directionके रूप में handle किया गया है - kernel driver का
gpu_fb_writeuser data को DMA में देने के लिए यह प्रक्रिया अपनाता हैkmalloc(count, GFP_KERNEL)से kernel buffer allocate करनाcopy_from_userसे user data को kernel buffer में copy करनाdma_map_single(&gpu->pdev->dev, kbuf, count, DMA_TO_DEVICE)से DMA address बनानाexecute_dma(gpu, DIR_HOST_TO_GPU, dma_addr, *offset, count)call करनाkfree(kbuf)से buffer free करना
- इस तरीके से गति इतनी बढ़ी कि example system में लगभग 300µs मापी गई
DMA completion को MSI-X interrupt से बताना
- DMA execution asynchronous है, इसलिए
writecomplete होने तक उसे block करना अधिक सुविधाजनक हो सकता है - PCI-e card Message Signalled Interrupts के जरिए CPU को signal भेज सकता है
- MSI, dedicated electrical connection का उपयोग करने वाले पारंपरिक interrupts से अलग, bus पर सामान्य message packets के रूप में interrupt भेजता है
- MSI-X setup के लिए QEMU device में दो regions रखे जाते हैं
- हर interrupt configuration को store करने वाली MSI-X table
- pending interrupt bitmap यानी PBA
- example constants इस प्रकार हैं
IRQ_COUNTहै1IRQ_DMA_DONE_NRहै0MSIX_ADDR_BASEहै0x1000PBA_ADDR_BASEहै0x3000
- QEMU के
pci_gpu_realizeमेंmsix_initऔरmsix_vector_usecall करके MSI-X initialize किया जाता है lspci -vvमें MSI-X enabled दिखता है, और vector table BAR0 offset00001000, PBA BAR0 offset00003000पर दिखते हैंpci_dma_readखत्म होने के बादmsix_notify(&gpu->pdev, IRQ_DMA_DONE_NR)call करके interrupt भेजा जाता है
Kernel IRQ handler और bus mastering
- kernel driver
pci_alloc_irq_vectorsसे MSI-X/MSI vectors allocate करता है औरpci_irq_vectorसे IRQ number प्राप्त करता है request_threaded_irqसेGPU-Dma0handler register किया जाता है- boot के बाद
/proc/interruptsमें उदाहरण की तरह IRQ24कोPCI-MSIX-0000:00:02.0औरGPU-Dma0के रूप में दिखाया जाता है - शुरुआत में यह काम नहीं करता, क्योंकि card के पास CPU को स्वतंत्र रूप से message भेजने की permission नहीं होती
- device को CPU intervention के बिना system memory को सीधे manipulate करने देने वाली capability को bus mastering कहा जाता है
- kernel के
gpu_probeमेंpci_set_master(pdev)call करने पर device को bus master permission मिल जाती है - इसके बाद
writeदो बार call करने पर kernel log मेंIRQ 24 receivedदो बार print होता है
wait queue से असली blocking write implement करना
- interrupt-based notification तैयार होने पर Linux wait queue से
writeको blocking call में बदला जा सकता है - global state के रूप में
wait_queue_head_t wqऔरvolatile int irq_fired = 0रखे जाते हैं - IRQ handler ये काम करता है
irq_fired = 1से completion state set करनाwake_up_interruptible(&wq)से waiting thread को जगानाIRQ_HANDLEDreturn करना
setup_msiमेंinit_waitqueue_head(&wq)जोड़ा जाता हैgpu_fb_writeDMA execute करने के बादwait_event_interruptible(wq, irq_fired != 0)से interrupt का इंतज़ार करता है- wait के दौरान interrupt होने पर
-ERESTARTSYSreturn किया जाता है
QEMU console में framebuffer दिखाना
- अब user space के
write(2)को लेकर DMA से PCI-e device तक भेजने वाला framebuffer मौजूद है, इसलिए इसे QEMU console output से जोड़कर काम करने वाले GPU की तरह दिखाया जाता है - QEMU के
GpuStateमेंQemuConsole* conजोड़ा जाता है pci_gpu_realizeमेंgraphic_console_initसे console बनाया जाता है, औरqemu_console_surfaceसे display surface लिया जाता है- शुरुआती test pattern को 640×480 range की surface data में values भरकर दिखाया जाता है
vga_update_displaygpu->framebuffercontents को QEMU display surface में copy करता हैdpy_gfx_update(gpu->con, 0, 0, 640, 480)से 640×480 region refresh किया जाता है- इसके बाद underlying device पर pattern लिखने से display बदलती है
- source code the Github repo में उपलब्ध है
1 टिप्पणियां
Hacker News की टिप्पणियाँ
शुरू करने के लिए Tang Mega 138k [0] खरीदा था, लेकिन documentation ज़्यादा नहीं है इसलिए समय लग रहा है
अगर PCI-e hard IP वाला कोई सस्ता FPGA board सुझा सकें तो अच्छा होगा
[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...
लेकिन बाहरी high-speed interface के नाम पर सिर्फ एक USB 3.1 Gen 1 है
https://shop.lambdaconcept.com/home/50-screamer-pcie-squirre...
Litefury “NVMe SSD” form factor (2280 Key M) वाला Xilinx Artix FPGA kit है, इसमें Xilinx XC7A100T है और इसकी कीमत 102 यूरो है
बाहरी high-speed LVDS input/output भी बस कुछ ही हैं
https://rhsresearch.com/collections/rhs-public/products/lite...
Vivado, एक professional software engineer के नज़रिए से, कोई “बेहतरीन” tool नहीं है, लेकिन FPGA development और implementation में यह निश्चित रूप से industry के top tier में है
Xilinx का PCIe device development path भी काफ़ी अच्छी तरह तैयार है
मैंने खुद Linux device driver के साथ सीधे काम नहीं किया है, लेकिन कुछ साल पहले दूसरे operating system पर कई PCIe driver पर काम किया था, इसलिए concepts बहुत परिचित लगते हैं
काश इस तरह का content और ज़्यादा हो
core point दिखाने जितना ही code रखा गया है, और step-by-step build-up वाला तरीका अच्छा है
ज़िंदगी भर कभी नया PCI device बनाना नहीं चाहा था, लेकिन अब थोड़ा मन हो रहा है, और शायद यही अच्छी technical writing की असली कसौटी है
मैं अपने project के लिए development और playtest environment बनाना चाहता था, लेकिन खोजने के लिए keywords तक नहीं पता थे; यह बिल्कुल वही था जिसकी ज़रूरत थी
बाकी दो भाग भी अच्छे थे, और उनमें boot service driver code को exit के बाद इस्तेमाल करने के तरीके, bus mastering, MSI-X जैसी वास्तविक चीज़ें, और कई छोटी लेकिन उपयोगी details थीं