PCI-e सीखना: ड्राइवर और DMA
पिछले भाग का सारांश
- पिछले भाग में एक सरल PCI-e डिवाइस इम्प्लीमेंट करके मैन्युअली address(
0xfe000000) का उपयोग करते हुए 32-bit इकाइयों में read और write करने का तरीका देखा गया था.
- प्रोग्रामेटिक तरीके से यह address पाने के लिए PCI subsystem से memory mapping की details मांगनी होती हैं.
ड्राइवर struct बनाना
struct pci_driver बनाना होता है, और इसके लिए supported device table तथा probe function की आवश्यकता होती है.
- supported device table device/vendor ID pair की array से बना होता है.
static struct pci_device_id gpu_id_tbl[] = {
{ PCI_DEVICE(0x1234, 0x1337) },
{ 0, },
};
probe function तब कॉल होता है जब device/vendor ID match करती है, और इसे डिवाइस के memory region को refer करने के लिए driver state को update करना चाहिए.
typedef struct GpuState {
struct pci_dev *pdev;
u8 __iomem *hwmem;
} GpuState;
probe function इम्प्लीमेंट करना
- डिवाइस को enable करके
pci_dev का reference store किया जाता है.
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;
}
user space में कार्ड expose करना
- अब जब kernel driver से BAR0 address space map हो चुका है, तो एक character device बनाया जा सकता है ताकि user space application file operations के माध्यम से PCIe डिवाइस के साथ interact कर सके.
open, read, write functions इम्प्लीमेंट करने होंगे.
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 का उपयोग
- CPU अगर एक बार में एक DWORD डेटा copy करे, तो उसकी जगह DMA का उपयोग करके कार्ड को डेटा खुद copy करने दिया जा सकता है.
- DMA "function call" interface की परिभाषा:
- CPU कार्ड को बताता है कि कौन-सा डेटा copy करना है (source address, length), destination address क्या है, और data flow direction क्या है (read या write).
- CPU कार्ड को बताता है कि अब वह copy शुरू कर सकता है.
- कार्ड CPU को सूचित करता है कि transfer पूरा हो गया है.
#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 सेटअप
- क्योंकि DMA execution asynchronous है, इसलिए
write को उसके पूरा होने तक block करना बेहतर है.
- PCI-e कार्ड message signaled interrupts(MSI) के जरिए CPU को signal भेज सकता है.
- MSI-X सेटअप करने के लिए हर interrupt के लिए configuration space(MSI-X table) और pending interrupts के bitmap(PBA) को store करने की जगह allocate करनी होती है.
#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;
}
वास्तव में block करने वाला write
- interrupt mechanism का उपयोग करके
write को block करने के लिए wait queue का उपयोग किया जा सकता है.
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;
}
स्क्रीन पर दिखाना
- अब user space में
write(2) के जरिए डेटा PCI-e डिवाइस तक पहुंचाने वाला एक 'framebuffer' मौजूद है.
- कार्ड के buffer को QEMU के console output से जोड़कर इसे काम करने वाले GPU जैसा दिखाया जा सकता है.
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⁺ का सार
- यह लेख PCI-e device driver और DMA पर केंद्रित है, और बताता है कि kernel driver के जरिए user space application को PCIe डिवाइस के साथ interact करने योग्य कैसे बनाया जाए.
- इसमें DMA का उपयोग करके CPU का load कम करने और data transfer speed बढ़ाने का तरीका समझाया गया है.
- DMA transfer पूरा होने पर CPU को signal भेजने के लिए MSI-X का उपयोग कैसे किया जाए, यह समझाया गया है.
- QEMU का उपयोग करके virtual environment में GPU को simulate और test करने का तरीका भी शामिल है.
- समान कार्यक्षमता वाले प्रोजेक्ट्स में
pciemu और Linux Kernel Labs - Device Drivers शामिल हैं.
1 टिप्पणियां
Hacker News टिप्पणी
अंतिम लक्ष्य FPGA का उपयोग करके एक display adapter बनाना है
इन लेखों का flow मुझे बहुत पसंद आया
यह Linux PCIe device driver के लिए एक बेहतरीन शुरुआती guide लगती है
यह लिखने के लिए आपका बहुत धन्यवाद