PCI-e öğrenimi: sürücüler ve DMA
Önceki bölüm özeti
- Önceki bölümde basit bir PCI-e aygıtı gerçeklenmiş ve
0xfe000000 adresi elle kullanılarak 32 bitlik okuma ve yazma işlemlerinin nasıl yapıldığı ele alınmıştı.
- Bu adresi programatik olarak almak için PCI alt sisteminden bellek eşleme ayrıntılarını istemek gerekir.
Sürücü yapısının oluşturulması
struct pci_driver oluşturulmalı; bunun için desteklenen aygıt tablosu ve bir probe işlevi gerekir.
- Desteklenen aygıt tablosu, aygıt/vendor ID çiftlerinden oluşan bir dizi halinde tanımlanır.
static struct pci_device_id gpu_id_tbl[] = {
{ PCI_DEVICE(0x1234, 0x1337) },
{ 0, },
};
probe işlevi, aygıt/vendor ID eşleştiğinde çağrılır ve sürücü durumunu aygıtın bellek alanına başvuracak şekilde güncellemelidir.
typedef struct GpuState {
struct pci_dev *pdev;
u8 __iomem *hwmem;
} GpuState;
probe işlevinin gerçeklenmesi
- Aygıt etkinleştirilir ve
pci_dev için bir başvuru saklanır.
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;
}
Kartın kullanıcı alanına açılması
- Artık çekirdek sürücüsünde BAR0 adres alanı eşlendiğine göre, kullanıcı alanı uygulamalarının dosya işlemleri üzerinden PCIe aygıtıyla etkileşime girebilmesi için bir karakter aygıtı oluşturulabilir.
open, read, write işlevleri gerçeklenmelidir.
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);
DMA kullanımı
- CPU'nun veriyi her seferinde tek bir DWORD kopyalaması yerine, DMA kullanılarak kartın veriyi kendi başına kopyalaması sağlanabilir.
- DMA "işlev çağrısı" arayüzünün tanımı:
- CPU, karta kopyalanacak veriyi (kaynak adresi, uzunluk), hedef adresi ve veri akış yönünü (okuma veya yazma) bildirir.
- CPU, karta kopyayı başlatmaya hazır olduğunu bildirir.
- Kart, CPU'ya aktarımın tamamlandığını bildirir.
#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);
}
MSI-X yapılandırması
- DMA yürütmesi eşzamansız olduğu için,
write tamamlanana kadar engellenmesi daha iyidir.
- PCI-e kartı, mesaj işaretli kesmeler (MSI) üzerinden CPU'ya sinyal gönderebilir.
- MSI-X kurmak için her kesme için yapılandırma alanını (MSI-X tablosu) ve bekleyen kesmelerin bitmap'ini (PBA) saklayacak alanı ayırmak gerekir.
#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;
}
Gerçekte engelleyen yazma
- Kesmeler mekanizması kullanılarak
write çağrısını engellemek için bir bekleme kuyruğu kullanılabilir.
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;
}
Ekranda gösterim
- Artık kullanıcı alanından
write(2) aracılığıyla veriyi PCI-e aygıtına aktarabilen bir "framebuffer" var.
- Kartın tamponu, çalışan bir GPU gibi görünmesi için QEMU'nun konsol çıkışına bağlanabilir.
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,
};
GN⁺ özeti
- Bu yazı, PCI-e aygıt sürücüleri ve DMA'yı ele alıyor; çekirdek sürücüsü üzerinden kullanıcı alanı uygulamalarının PCIe aygıtıyla nasıl etkileşebileceğini açıklıyor.
- DMA kullanarak CPU yükünün nasıl azaltılacağı ve veri aktarım hızının nasıl artırılacağı anlatılıyor.
- DMA aktarımı tamamlandığında MSI-X kullanarak CPU'ya nasıl sinyal gönderileceği açıklanıyor.
- QEMU kullanarak sanal ortamda GPU'nun nasıl simüle edilip test edileceği ele alınıyor.
- Benzer işlevlere sahip projeler arasında
pciemu ve Linux Kernel Labs - Device Drivers yer alıyor.
1 yorum
Hacker News görüşü
Nihai hedef, FPGA kullanarak bir ekran bağdaştırıcısı yapmak
Bu yazı dizisinin akışını gerçekten çok beğeniyorum
Linux PCIe aygıt sürücüleri için harika bir giriş yazısı gibi görünüyor
Bunu yazdığınız için gerçekten teşekkürler
bootsvcsürücüsünün kullanımı, bus mastering, MSI-X gibi birçok faydalı ayrıntı içeriyor