Pembelajaran PCI-e: Driver dan DMA
Ringkasan bagian sebelumnya
- Pada bagian sebelumnya, dibuat perangkat PCI-e sederhana dan dibahas cara membaca serta menulis 32-bit secara manual menggunakan alamat (
0xfe000000).
- Untuk mendapatkan alamat ini secara terprogram, detail pemetaan memori harus diminta dari subsistem PCI.
Membuat struct driver
- Perlu membuat
struct pci_driver, serta tabel perangkat yang didukung dan fungsi probe.
- Tabel perangkat yang didukung terdiri dari array pasangan device/vendor ID.
static struct pci_device_id gpu_id_tbl[] = {
{ PCI_DEVICE(0x1234, 0x1337) },
{ 0, },
};
- Fungsi
probe dipanggil ketika device/vendor ID cocok, dan harus memperbarui status driver agar mereferensikan area memori perangkat.
typedef struct GpuState {
struct pci_dev *pdev;
u8 __iomem *hwmem;
} GpuState;
Implementasi fungsi probe
- Mengaktifkan perangkat dan menyimpan referensi ke
pci_dev.
static int gpu_probe(struct pci_dev *pdev, const struct pci_device_id *id) {
int bars;
unsigned long mmio_start, mmio_len;
GpuState* gpu = kmalloc(sizeof(struct GpuState), GFP_KERNEL);
gpu->pdev = pdev;
pci_enable_device_mem(pdev);
bars = pci_select_bars(pdev, IORESOURCE_MEM);
pci_request_region(pdev, bars, "gpu-pci");
mmio_start = pci_resource_start(pdev, 0);
mmio_len = pci_resource_len(pdev, 0);
gpu->hwmem = ioremap(mmio_start, mmio_len);
return 0;
}
Mengekspos kartu ke user space
- Sekarang karena driver kernel telah memetakan ruang alamat BAR0, perangkat karakter dapat dibuat agar aplikasi user space bisa berinteraksi dengan perangkat PCIe melalui operasi file.
- Perlu mengimplementasikan fungsi
open, read, dan write.
static int gpu_open(struct inode *inode, struct file *file);
static ssize_t gpu_read(struct file *file, char __user *buf, size_t count, loff_t *offset);
static ssize_t gpu_write(struct file *file, const char __user *buf, size_t count, loff_t *offset);
Menggunakan DMA
- Alih-alih CPU menyalin data satu DWORD setiap kali, DMA dapat digunakan agar kartu menyalin data sendiri.
- Definisi antarmuka "pemanggilan fungsi" DMA:
- CPU memberi tahu kartu data yang akan disalin (alamat sumber, panjang), alamat tujuan, dan arah aliran data (baca atau tulis).
- CPU memberi tahu kartu bahwa ia siap memulai penyalinan.
- Kartu memberi tahu CPU bahwa transfer telah selesai.
#define REG_DMA_DIR 0
#define REG_DMA_ADDR_SRC 1
#define REG_DMA_ADDR_DST 2
#define REG_DMA_LEN 3
#define CMD_ADDR_BASE 0xf00
#define CMD_DMA_START (CMD_ADDR_BASE + 0)
static void write_reg(GpuState* gpu, u32 val, u32 reg) {
iowrite32(val, gpu->hwmem + (reg * sizeof(u32)));
}
void execute_dma(GpuState* gpu, u8 dir, u32 src, u32 dst, u32 len) {
write_reg(gpu, dir, REG_DMA_DIR);
write_reg(gpu, src, REG_DMA_ADDR_SRC);
write_reg(gpu, dst, REG_DMA_ADDR_DST);
write_reg(gpu, len, REG_DMA_LEN);
write_reg(gpu, 1, CMD_DMA_START);
}
Menyiapkan MSI-X
- Karena eksekusi DMA bersifat asinkron, lebih baik memblokir
write sampai selesai.
- Kartu PCI-e dapat mengirim sinyal ke CPU melalui message signaled interrupts (MSI).
- Untuk menyiapkan MSI-X, ruang harus dialokasikan untuk configuration space (tabel MSI-X) bagi tiap interupsi dan bitmap interupsi yang sedang tertunda (PBA).
#define IRQ_COUNT 1
#define IRQ_DMA_DONE_NR 0
#define MSIX_ADDR_BASE 0x1000
#define PBA_ADDR_BASE 0x3000
static irqreturn_t irq_handler(int irq, void *data) {
pr_info("IRQ %d received\n", irq);
return IRQ_HANDLED;
}
static int setup_msi(GpuState* gpu) {
int msi_vecs;
int irq_num;
msi_vecs = pci_alloc_irq_vectors(gpu->pdev, IRQ_COUNT, IRQ_COUNT, PCI_IRQ_MSIX | PCI_IRQ_MSI);
irq_num = pci_irq_vector(gpu->pdev, IRQ_DMA_DONE_NR);
request_threaded_irq(irq_num, irq_handler, NULL, 0, "GPU-Dma0", gpu);
return 0;
}
Write yang benar-benar memblokir
- Dengan mekanisme interupsi, antrean tunggu dapat digunakan untuk memblokir
write.
wait_queue_head_t wq;
volatile int irq_fired = 0;
static irqreturn_t irq_handler(int irq, void *data) {
irq_fired = 1;
wake_up_interruptible(&wq);
return IRQ_HANDLED;
}
static ssize_t gpu_fb_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) {
GpuState *gpu = (GpuState*) file->private_data;
dma_addr_t dma_addr;
u8* kbuf = kmalloc(count, GFP_KERNEL);
copy_from_user(kbuf, buf, count);
dma_addr = dma_map_single(&gpu->pdev->dev, kbuf, count, DMA_TO_DEVICE);
execute_dma(gpu, DIR_HOST_TO_GPU, dma_addr, *offset, count);
if (wait_event_interruptible(wq, irq_fired != 0)) {
pr_info("interrupted");
return -ERESTARTSYS;
}
kfree(kbuf);
return count;
}
Menampilkan ke layar
- Sekarang ada sebuah 'framebuffer' yang dapat menerima data dari user space ke perangkat PCI-e melalui
write(2).
- Buffer kartu dapat dihubungkan ke output konsol QEMU agar terlihat seperti GPU yang berfungsi.
struct GpuState {
PCIDevice pdev;
MemoryRegion mem;
QemuConsole* con;
uint32_t registers[0x100000 / 32];
uint32_t framebuffer[0x200000];
};
static void pci_gpu_realize(PCIDevice *pdev, Error **errp) {
gpu->con = graphic_console_init(DEVICE(pdev), 0, &ghwops, gpu);
DisplaySurface *surface = qemu_console_surface(gpu->con);
for(int i = 0; i<640*480; i++) {
((uint32_t*)surface_data(surface))[i] = i;
}
}
static void vga_update_display(void *opaque) {
GpuState* gpu = opaque;
DisplaySurface *surface = qemu_console_surface(gpu->con);
for(int i = 0; i<640*480; i++) {
((uint32_t*)surface_data(surface))[i] = gpu->framebuffer[i % 0x200000 ];
}
dpy_gfx_update(gpu->con, 0, 0, 640, 480);
}
static const GraphicHwOps ghwops = {
.gfx_update = vga_update_display,
};
Ringkasan GN⁺
- Artikel ini membahas driver perangkat PCI-e dan DMA, serta menjelaskan cara agar aplikasi user space dapat berinteraksi dengan perangkat PCIe melalui driver kernel.
- Dibahas cara menggunakan DMA untuk mengurangi beban CPU dan meningkatkan kecepatan transfer data.
- Dijelaskan cara menggunakan MSI-X untuk mengirim sinyal ke CPU saat transfer DMA selesai.
- Dibahas cara mensimulasikan dan menguji GPU di lingkungan virtual menggunakan QEMU.
- Proyek dengan fungsi serupa antara lain
pciemu dan Linux Kernel Labs - Device Drivers.
1 komentar
Opini Hacker News
Tujuan akhirnya adalah membuat adaptor display menggunakan FPGA
Sangat menyukai alur rangkaian artikel ini
Ini tampak seperti pengantar yang sangat bagus untuk driver perangkat PCIe di Linux
Terima kasih banyak telah menulis ini