Mempelajari PCI-e: Driver dan DMA
(blog.davidv.dev)- Beralih dari tahap melakukan peek/poke langsung ke alamat BAR0 yang di-hardcode, driver kernel kini menemukan memori BAR melalui subsistem PCI Linux dan menginisialisasi perangkat
- Driver dimulai dari tabel ID pada
struct pci_driverdan fungsiprobe; setelah memetakan BAR0 ke alamat virtual kernel, ia menyiapkan akses dari ruang pengguna - Menghubungkan
read(2)danwrite(2)melalui perangkat karakter/dev/gpu-io, lalu memakaicontainer_ofuntuk mengambil kembali status driver dari operasi file - Penyalinan per DWORD memerlukan sekitar 800ms untuk transfer 1,2MiB, tetapi setelah diganti menjadi pemanggilan DMA berbasis register MMIO, turun hingga sekitar 300µs
- Penantian selesainya DMA ditangani dengan interupsi MSI-X dan wait queue; pada akhirnya bekerja sebagai GPU palsu yang menampilkan isi framebuffer di konsol QEMU
Menemukan dan memetakan BAR0 dari driver kernel
- Implementasi sebelumnya membaca dan menulis langsung per 32-bit ke alamat BAR0
0xfe000000yang disalin darilspci - Agar alamat tidak di-hardcode, informasi pemetaan memori perangkat diambil dari subsistem PCI Linux
struct pci_drivermembutuhkan dua field inti- Tabel pasangan device/vendor ID yang didukung
- Fungsi
probeyang dipanggil ketika ID cocok
- Perangkat contoh cocok dengan
PCI_DEVICE(0x1234, 0x1337) - Status driver
GpuStatemenyimpanstruct pci_dev *pdevdanu8 __iomem * hwmemuntuk memori BAR - Fungsi
probemenyiapkan perangkat dengan urutan berikut- Mengaktifkan akses memori perangkat dengan
pci_enable_device_mem(pdev) - Mendapatkan bitfield BAR memori yang tersedia dengan
pci_select_bars(pdev, IORESOURCE_MEM) - Meminta kepemilikan ruang alamat BAR dengan
pci_request_region(pdev, bars, "gpu-pci") - Mendapatkan alamat awal dan panjang BAR0 dengan
pci_resource_start(pdev, 0)danpci_resource_len(pdev, 0) - Memetakan alamat fisik ke alamat virtual kernel dengan
ioremap(mmio_start, mmio_len)
- Mengaktifkan akses memori perangkat dengan
- Jika
pci_register_driverdipanggil darimodule_init, log boot akan mencetakmmio starts at 0xfe000000dan alamat virtual kernel
Mengeksposnya ke ruang pengguna sebagai perangkat karakter
- Setelah ruang alamat BAR0 dipetakan ke driver kernel, dibuat perangkat karakter agar program ruang pengguna dapat berinteraksi dengan perangkat PCIe melalui
read(2)danwrite(2) - Driver ini hanya membutuhkan tiga operasi file:
open,read, danwrite - Tambahkan
struct cdev cdevkeGpuState, lalu lakukan pekerjaan berikut disetup_chardev- Mengalokasikan nomor perangkat dengan
alloc_chrdev_region - Mendaftarkan perangkat karakter dengan
cdev_initdancdev_add - Membuat
/dev/gpu-iodengandevice_create
- Mengalokasikan nomor perangkat dengan
- Tambahkan
/busybox mdev -ske skrip init untuk mengisi pseudo-filesystem/dev/ - Setelah itu
/dev/gpu-ioterlihat sebagai perangkat karakter; pada contoh, muncul dengan nomor major241dan minor0
Menemukan status driver dari operasi file dengan container_of
- Dalam implementasi
write,private_datamilikstruct file*harus diisi olehopen, tetapiopentidak menerima argumenprivate_dataatauuser_dataterpisah struct inodememiliki pointerstruct cdev *i_cdevyang menunjuk ke perangkat karakter- Karena
GpuStatemenyematkanstruct cdev, pointerGpuStatedapat diambil kembali dengancontainer_of(inode->i_cdev, struct GpuState, cdev) gpu_openmenyimpanGpuStateyang didapat kefile->private_data- Setelah itu
gpu_readdangpu_writemengambilGpuStatedarifile->private_datadan menggunakannya read/writeawal memproses satu DWORD setiap kaligpu_readmembaca denganioread32(gpu->hwmem + *offset)lalu menyalinnya ke buffer pengguna dengancopy_to_usergpu_writemenyalin 4 byte dari buffer pengguna dan menaikkan offset sebesar 4
- Ini bekerja untuk transfer kecil, tetapi lambat untuk transfer besar karena CPU harus terus memproses paket satu per satu
- Transfer 1,2MiB yang setara dengan 640×480, 32bpp memerlukan sekitar
800ms
Membuat pemanggilan DMA dengan register MMIO
- Alih-alih CPU mengulang penyalinan per DWORD, DMA digunakan agar perangkat menyalin data secara langsung
- Permintaan pekerjaan dikirim dengan metode memory-mapped IO
- Beberapa alamat memori digunakan seperti register yang berperan sebagai argumen pemanggilan DMA
- Alamat lain digunakan seperti perintah yang berarti menjalankan pemanggilan fungsi
- Antarmuka DMA memiliki nilai yang harus diberitahukan CPU kepada perangkat
- Alamat source data yang akan disalin dan panjangnya
- Alamat destination
- Arah data: menuju main memory atau dari main memory
- Sinyal bahwa penyalinan siap dimulai
- Perangkat harus memberi tahu CPU bahwa transfer telah selesai
- Register contoh didefinisikan sebagai berikut
REG_DMA_DIRREG_DMA_ADDR_SRCREG_DMA_ADDR_DSTREG_DMA_LEN
CMD_DMA_STARTdigunakan sebagai alamat perintah yang membedakan operasi mengisi nilai register dari benar-benar memulai DMAexecute_dmapada driver kernel menulis arah, source, destination, dan panjang denganiowrite32, lalu terakhir menulis1keCMD_DMA_START
Pemrosesan DMA di sisi perangkat QEMU
- MMIO
gpu_writepada adaptor QEMU menggantikan implementasi sebelumnya untuk menangani register dan perintah DMA - Penulisan ke area register menyimpan nilai ke
gpu->registers[reg] - Ketika
REG_DMA_STARTmasuk di area perintah, arah DMA diperiksa - Pada arah
DIR_HOST_TO_GPU,pci_dma_readdipanggil- Alamat host adalah
REG_DMA_ADDR_SRC - Alamat device adalah
gpu->framebuffer + REG_DMA_ADDR_DST - Panjangnya adalah
REG_DMA_LEN
- Alamat host adalah
- Arah DMA lain ditangani sebagai
Unimplemented DMA directionpada kode contoh gpu_fb_writepada driver kernel meneruskan data pengguna ke DMA dengan prosedur berikut- Mengalokasikan buffer kernel dengan
kmalloc(count, GFP_KERNEL) - Menyalin data pengguna ke buffer kernel dengan
copy_from_user - Membuat alamat DMA dengan
dma_map_single(&gpu->pdev->dev, kbuf, count, DMA_TO_DEVICE) - Memanggil
execute_dma(gpu, DIR_HOST_TO_GPU, dma_addr, *offset, count) - Membebaskan buffer dengan
kfree(kbuf)
- Mengalokasikan buffer kernel dengan
- Cara ini menjadi jauh lebih cepat, terukur sekitar 300µs pada sistem contoh
Memberi tahu penyelesaian DMA dengan interupsi MSI-X
- Karena eksekusi DMA bersifat asinkron, akan lebih praktis jika
writedibuat memblokir sampai selesai - Kartu PCI-e dapat mengirim sinyal ke CPU dengan Message Signalled Interrupts
- Berbeda dari interupsi klasik yang memakai koneksi elektrik khusus, MSI mengirimkan interupsi sebagai paket pesan biasa di atas bus
- Untuk konfigurasi MSI-X, perangkat QEMU memiliki dua area
- MSI-X table yang menyimpan konfigurasi tiap interupsi
- PBA, yaitu pending interrupt bitmap
- Konstanta contoh adalah sebagai berikut
IRQ_COUNTadalah1IRQ_DMA_DONE_NRadalah0MSIX_ADDR_BASEadalah0x1000PBA_ADDR_BASEadalah0x3000
- Di
pci_gpu_realizemilik QEMU,msix_initdanmsix_vector_usedipanggil untuk menginisialisasi MSI-X - Pada
lspci -vv, MSI-X terlihat aktif, vector table ditampilkan sebagai offset BAR000001000, dan PBA sebagai offset BAR000003000 - Setelah
pci_dma_readselesai, interupsi dikirim dengan memanggilmsix_notify(&gpu->pdev, IRQ_DMA_DONE_NR)
Handler IRQ kernel dan bus mastering
- Driver kernel mengalokasikan vektor MSI-X/MSI dengan
pci_alloc_irq_vectorsdan memperoleh nomor IRQ denganpci_irq_vector - Handler
GPU-Dma0didaftarkan denganrequest_threaded_irq - Setelah boot,
/proc/interruptsmenampilkan IRQ24sebagaiPCI-MSIX-0000:00:02.0danGPU-Dma0, seperti pada contoh - Awalnya tidak berjalan, karena kartu tidak memiliki izin untuk mengirim pesan secara independen ke CPU
- Fitur yang memungkinkan perangkat memanipulasi memori sistem secara langsung tanpa intervensi CPU disebut bus mastering
- Jika
pci_set_master(pdev)dipanggil digpu_probekernel, perangkat diberi hak bus master - Setelah itu, jika
writedipanggil dua kali, log kernel akan mencetakIRQ 24 receiveddua kali
Mengimplementasikan blocking write sungguhan dengan wait queue
- Setelah notifikasi berbasis interupsi siap,
writedapat diubah menjadi panggilan blocking dengan wait queue Linux - Sebagai status global, disediakan
wait_queue_head_t wqdanvolatile int irq_fired = 0 - Handler IRQ melakukan pekerjaan berikut
- Menetapkan status selesai dengan
irq_fired = 1 - Membangunkan thread yang sedang menunggu dengan
wake_up_interruptible(&wq) - Mengembalikan
IRQ_HANDLED
- Menetapkan status selesai dengan
- Tambahkan
init_waitqueue_head(&wq)kesetup_msi - Setelah menjalankan DMA,
gpu_fb_writemenunggu interupsi denganwait_event_interruptible(wq, irq_fired != 0) - Jika terinterupsi saat menunggu, ia mengembalikan
-ERESTARTSYS
Menampilkan framebuffer di konsol QEMU
- Karena kini ada framebuffer yang menerima
write(2)dari ruang pengguna dan meneruskannya ke perangkat PCI-e lewat DMA, framebuffer tersebut dihubungkan ke output konsol QEMU agar tampak seperti GPU yang berfungsi - Tambahkan
QemuConsole* conkeGpuStatemilik QEMU - Di
pci_gpu_realize, buat konsol dengangraphic_console_init, lalu dapatkan display surface denganqemu_console_surface - Pola uji awal ditampilkan dengan mengisi nilai ke data surface pada rentang 640×480
vga_update_displaymenyalin isigpu->framebufferke display surface QEMU- Area 640×480 diperbarui dengan
dpy_gfx_update(gpu->con, 0, 0, 640, 480) - Setelah itu, jika pola ditulis ke underlying device, tampilan berubah
- Kode sumber tersedia di the Github repo
1 komentar
Komentar Hacker News
Saya sudah membeli Tang Mega 138k [0] untuk memulai, tetapi dokumentasinya tidak banyak, jadi butuh waktu.
Kalau ada rekomendasi board FPGA murah yang punya hard IP PCI-e, akan sangat membantu.
[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...
Namun antarmuka eksternal berkecepatan tingginya hanya satu USB 3.1 Gen 1.
https://shop.lambdaconcept.com/home/50-screamer-pcie-squirre...
Litefury adalah kit FPGA Xilinx Artix dalam form factor “NVMe SSD” (2280 Key M), memakai Xilinx XC7A100T, dan harganya 102 euro.
Input/output LVDS eksternal berkecepatan tingginya hanya beberapa saja.
https://rhsresearch.com/collections/rhs-public/products/lite...
Vivado memang bukan alat yang “luar biasa” menurut standar software engineer profesional, tetapi untuk pengembangan dan implementasi FPGA, jelas berada di level terbaik industri.
Jalur pengembangan perangkat PCIe dari Xilinx juga sudah cukup matang.
Saya belum pernah langsung menangani driver perangkat Linux, tetapi beberapa tahun lalu saya pernah mengerjakan beberapa driver PCIe di sistem operasi lain, dan konsepnya terasa sangat familier.
Saya berharap konten seperti ini semakin banyak.
Kodenya hanya sebanyak yang diperlukan untuk menunjukkan inti, lalu dibangun tahap demi tahap.
Seumur hidup saya tidak pernah ingin membuat perangkat PCI baru, tetapi sekarang jadi sedikit ingin membuatnya; rasanya ini semacam uji asam untuk tulisan teknis yang bagus.
Saya ingin membuat lingkungan pengembangan dan playtest untuk proyek, tetapi bahkan tidak tahu kata kunci pencariannya; ini persis yang saya butuhkan.
Dua bagian lainnya juga bagus, dengan banyak materi praktis seperti cara menggunakan kode driver boot service setelah exit, bus mastering, MSI-X, serta detail-detail kecil tetapi berguna.