Introduction
This post walks through the design and implementation of a complete Linux PCI
device driver with DMA. We will use the emulated PCI device edu in QEMU.
QEMU edu Device Register Map (BAR0)
| Offset | Name | R/W | Width | Description |
|---|---|---|---|---|
0x00 |
Identification | RO | u32 | Device magic / version ID |
0x04 |
Liveness | RW | u32 | Write X, read back ~X (bitwise inverse) |
0x08 |
Factorial | RW | u32 | Write n, read back n! (async computation) |
0x20 |
Status | RW | u32 | Bit 0: computing, Bit 7: arm factorial IRQ |
0x24 |
IRQ Status | RO | u32 | Bit 0: factorial done, Bit 8: DMA done |
0x60 |
IRQ Raise | WO | u32 | OR value into IRQ status, fires interrupt |
0x64 |
IRQ Acknowledge | WO | u32 | Clear bits in IRQ status |
0x80 |
DMA Source | RW | u32 | Source physical address |
0x88 |
DMA Destination | RW | u32 | Destination physical address |
0x90 |
DMA Count | RW | u32 | Transfer byte count (max 4096) |
0x98 |
DMA Command | WO | u32 | Bit 0: start, Bit 1: dev-to-RAM, Bit 2: IRQ on done |
The device has a 4 KiB internal buffer at device-local address 0x40000.
DMA transfers move data between guest RAM and this internal buffer.
Design overview
┌─────────────────────────────────────────────────────────────────────┐
│ Userspace │
│ ┌──────────┐ │
│ │ edu_test │ open("/dev/edu0") + ioctl(EDU_IOCTL_DMA, &op) │
│ └────┬─────┘ │
│───────┼─────────────────────────────────────────────────────────────│
│ Kernel │
│ ┌────▼──────────────────────────────────────────────────────────┐ │
│ │ edu_drv.ko │ │
│ │ │ │
│ │ module_init ──► alloc_chrdev_region ──► class_create │ │
│ │ pci_register_driver │ │
│ │ │ │ │
│ │ probe() ◄──────────────┘ (called per matching PCI device) │ │
│ │ ├─ pcim_enable_device Enable PCI config space │ │
│ │ ├─ pcim_iomap_regions Map BAR0 into kernel VA │ │
│ │ ├─ dma_set_mask_and_coherent Set 28-bit DMA addressing │ │
│ │ ├─ pci_set_master Enable bus mastering │ │
│ │ ├─ dma_alloc_coherent Alloc 4K DMA buffer │ │
│ │ ├─ pci_alloc_irq_vectors Request MSI interrupt │ │
│ │ ├─ devm_request_irq Register ISR │ │
│ │ └─ cdev_add + device_create Create /dev/edu0 │ │
│ │ │ │
│ │ ioctl(DMA) │ │
│ │ ├─ copy_from_user ──► dma_buf Fill DMA buffer │ │
│ │ ├─ edu_reg_write(SRC, DST, ..) Program DMA registers │ │
│ │ ├─ edu_reg_write(CMD) Trigger DMA engine │ │
│ │ ├─ wait_event(irq_wq) Sleep for completion │ │
│ │ │ ▲ │ │
│ │ │ IRQ ─┘ edu_irq_handler: │ │
│ │ │ read IRQ_STATUS, ACK, wake_up() │ │
│ │ └─ copy_to_user ◄── dma_buf Return data │ │
│ │ │ │
│ │ remove() │ │
│ │ ├─ device_destroy + cdev_del │ │
│ │ ├─ pci_free_irq_vectors │ │
│ │ └─ dma_free_coherent │ │
│ └───────────────────────────────────────────────────────────────┘ │
│─────────────────────────────────────────────────────────────────────│
│ Hardware (QEMU edu device on PCIe bus) │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ BAR0 MMIO registers │ 4 KiB internal DMA buffer │ │
│ │ 0x00: ID │ @ device address 0x40000 │ │
│ │ 0x08: Factorial engine │ │ │
│ │ 0x80-0x98: DMA engine │ MSI interrupt controller │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Step 1: Define the PCI ID table and driver structure
Every PCI driver starts by declaring which devices it supports. The kernel uses this table to match hardware to driver at boot or hotplug time.
/* edu_drv.c */
#include <linux/pci.h>
#define EDU_VENDOR_ID 0x1234
#define EDU_DEVICE_ID 0x11e8
static const struct pci_device_id edu_pci_ids[] = {
{ PCI_DEVICE(EDU_VENDOR_ID, EDU_DEVICE_ID) },
{ 0, } /* sentinel */
};
MODULE_DEVICE_TABLE(pci, edu_pci_ids);
static struct pci_driver edu_pci_driver = {
.name = "edu_pci",
.id_table = edu_pci_ids,
.probe = edu_probe, /* called when device is found */
.remove = edu_remove, /* called on rmmod or hot-unplug */
};
MODULE_DEVICE_TABLE exports the IDs so that depmod/modprobe can
auto-load the driver when the hardware is detected.
Step 2: Enable the device and map BAR0 (MMIO)
In probe(), enable the PCI device and map its Base Address Register (BAR)
into the kernel’s virtual address space so you can read/write hardware registers.
static int edu_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct edu_dev *edu;
int ret;
edu = devm_kzalloc(&pdev->dev, sizeof(*edu), GFP_KERNEL);
/* Enable PCI device (managed — auto-disables on driver detach) */
ret = pcim_enable_device(pdev);
/* Request and map BAR0 region */
ret = pcim_iomap_regions(pdev, BIT(0), "edu_pci");
edu->bar0 = pcim_iomap_table(pdev)[0];
/* Now you can do MMIO: */
u32 id_reg = ioread32(edu->bar0 + 0x00); /* read ID register */
iowrite32(0x42, edu->bar0 + 0x04); /* write alive register */
Key concepts:
pcim_enable_device()— managed variant; resources freed automatically on driver detach. Preferpcim_*overpci_*to avoid manual cleanup.pcim_iomap_regions(pdev, BIT(n), name)— requests exclusive access to BARnand maps it to kernel virtual memory in one call.ioread32()/iowrite32()— portable MMIO accessors. Never use raw pointer dereference on__iomemmemory.
Step 3: Set DMA mask and enable bus mastering
Before any DMA, you must tell the kernel what address range the device supports and enable bus mastering so the device can initiate memory transfers.
/* The edu device uses 32-bit addressing (1024 MB) */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
/* Enable bus mastering — required for the device to perform DMA */
pci_set_master(pdev);
Key concepts:
dma_set_mask_and_coherent()— sets both streaming and coherent DMA masks. This tells the kernel the highest physical address the device can drive on the bus. The kernel may use bounce buffers if system RAM is above this limit.pci_set_master()— sets the PCI_COMMAND_MASTER bit in PCI config space, allowing the device to become a bus master.
Step 4: Allocate DMA buffer
Allocate a buffer that is simultaneously accessible by the CPU (via virtual address) and by the device (via bus/physical address).
edu->dma_buf = dma_alloc_coherent(&pdev->dev,
4096, /* size */
&edu->dma_handle, /* output: bus addr */
GFP_KERNEL);
/* edu->dma_buf = kernel virtual address (for CPU access) */
/* edu->dma_handle = bus/physical address (write to device DMA regs) */
Key concepts:
- Coherent DMA — the buffer is permanently mapped and always cache-coherent between CPU and device. No explicit sync needed. Use for control structures, descriptors, and small buffers.
- Streaming DMA (
dma_map_single()) — maps an existing buffer temporarily. Requiresdma_sync_*calls to maintain cache coherency. Use for large, high-throughput data paths (network packets, storage blocks). - The returned
dma_handleis what you program into the device’s DMA address registers. It may differ from the CPU physical address if an IOMMU is active.
Step 5: Set up MSI interrupts
Modern PCI devices use Message Signaled Interrupts (MSI/MSI-X) instead of legacy INTx pin-based interrupts.
/* Request 1 MSI vector (fall back to legacy if MSI unavailable) */
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY);
edu->irq = pci_irq_vector(pdev, 0); /* get Linux IRQ number */
ret = devm_request_irq(&pdev->dev, edu->irq,
edu_irq_handler, /* ISR function */
0, /* flags */
"edu_pci", /* /proc/interrupts name */
edu); /* dev_id passed to ISR */
Key concepts:
pci_alloc_irq_vectors()— allocates MSI/MSI-X vectors. The min/max parameters let you request a range; the kernel picks what’s available.PCI_IRQ_MSI | PCI_IRQ_LEGACYmeans “prefer MSI, fall back to legacy.”- The ISR runs in hard IRQ context — no sleeping, no mutexes, minimal work. Read the device status, acknowledge the interrupt, and wake a waitqueue.
Step 6: Implement the interrupt handler
The ISR reads which interrupt fired, acknowledges it on the device, and wakes any sleeping threads.
static irqreturn_t edu_irq_handler(int irq, void *data)
{
struct edu_dev *edu = data;
u32 status;
status = ioread32(edu->bar0 + EDU_REG_IRQ_STATUS);
if (!status)
return IRQ_NONE; /* not our interrupt */
edu->irq_status |= status; /* latch */
iowrite32(status, edu->bar0 + EDU_REG_IRQ_ACK); /* acknowledge */
wake_up_interruptible(&edu->irq_wq); /* wake waiting threads */
return IRQ_HANDLED;
}
Key concepts:
- Return
IRQ_NONEif this wasn’t your device’s interrupt (shared IRQ lines). - Latch the status — the ISR sets bits and the process-context code checks/
clears them. This avoids race conditions with
wait_event. wake_up_interruptible()— wakes threads sleeping inwait_event_interruptible().
Step 7: Perform a DMA transfer
The complete DMA flow: program the device’s DMA registers, trigger the transfer, then sleep until the completion interrupt arrives.
static int edu_do_dma(struct edu_dev *edu, u32 len, int from_dev)
{
u32 cmd;
edu->irq_status &= ~EDU_IRQ_DMA_DONE; /* clear stale status */
if (from_dev) {
/* Device internal buffer (0x40000) -> our DMA buffer */
iowrite32(0x40000, edu->bar0 + EDU_REG_DMA_SRC);
iowrite32(edu->dma_handle, edu->bar0 + EDU_REG_DMA_DST);
cmd = EDU_DMA_START | EDU_DMA_FROM_DEV | EDU_DMA_IRQ;
} else {
/* Our DMA buffer -> device internal buffer (0x40000) */
iowrite32(edu->dma_handle, edu->bar0 + EDU_REG_DMA_SRC);
iowrite32(0x40000, edu->bar0 + EDU_REG_DMA_DST);
cmd = EDU_DMA_START | EDU_DMA_TO_DEV | EDU_DMA_IRQ;
}
iowrite32(len, edu->bar0 + EDU_REG_DMA_COUNT);
iowrite32(cmd, edu->bar0 + EDU_REG_DMA_CMD); /* fire! */
/* Sleep until ISR wakes us (or timeout after 5s) */
ret = wait_event_interruptible_timeout(edu->irq_wq,
edu->irq_status & EDU_IRQ_DMA_DONE,
msecs_to_jiffies(5000));
if (ret == 0) return -ETIMEDOUT;
if (ret < 0) return ret; /* signal interrupted */
edu->irq_status &= ~EDU_IRQ_DMA_DONE;
return 0;
}
DMA data path for a round-trip test:
Write path: userspace buf ──copy_from_user──► dma_buf ──DMA──► device buffer
Read path: device buffer ──DMA──► dma_buf ──copy_to_user──► userspace buf
Step 8: Expose to userspace via character device
Create a /dev/eduN node so userspace programs can interact with the driver
using standard open() / ioctl() / close() calls.
/* In module_init: */
alloc_chrdev_region(&edu_devno_base, 0, 4, "edu_pci");
edu_class = class_create("edu_pci");
/* In probe() — per device: */
cdev_init(&edu->cdev, &edu_fops);
cdev_add(&edu->cdev, edu->devno, 1);
device_create(edu_class, &pdev->dev, edu->devno, NULL, "edu%d", idx);
/* This creates /dev/edu0, /dev/edu1, ... via udev/devtmpfs */
The ioctl dispatch table provides a clean userspace API:
| ioctl | Direction | Description |
|---|---|---|
EDU_IOCTL_GET_ID |
_IOR |
Read device ID register |
EDU_IOCTL_ALIVE |
_IOWR |
Liveness check (write X, get ~X) |
EDU_IOCTL_FACTORIAL |
_IOWR |
Compute n! with IRQ notification |
EDU_IOCTL_DMA |
_IOWR |
DMA transfer (read or write) |
EDU_IOCTL_IRQ_RAISE |
_IOW |
Manually raise an interrupt |
Step 9: Clean up in remove()
Release resources in reverse order of acquisition. Resources allocated with
devm_* / pcim_* are freed automatically, but non-managed resources need
explicit cleanup.
static void edu_remove(struct pci_dev *pdev)
{
struct edu_dev *edu = pci_get_drvdata(pdev);
device_destroy(edu_class, edu->devno); /* remove /dev/eduN */
cdev_del(&edu->cdev); /* unregister char device */
pci_free_irq_vectors(pdev); /* release MSI vectors */
dma_free_coherent(&pdev->dev, 4096, /* free DMA buffer */
edu->dma_buf, edu->dma_handle);
}
The Full Source Code
/*
* edu_regs.h - QEMU edu device register definitions
*
* Based on QEMU docs/specs/edu.txt and hw/misc/edu.c
* PCI Vendor: 0x1234 Device: 0x11e8
* BAR0: 1 MB MMIO region
*/
#ifndef _EDU_REGS_H_
#define _EDU_REGS_H_
/* PCI IDs */
#define EDU_VENDOR_ID 0x1234
#define EDU_DEVICE_ID 0x11e8
/* BAR0 Register offsets */
#define EDU_REG_ID 0x00 /* RO: Identification (returns 0x010000ed) */
#define EDU_REG_ALIVE 0x04 /* RW: Liveness check (reads back ~written) */
#define EDU_REG_FACTORIAL 0x08 /* RW: Write n, read n! when done */
#define EDU_REG_STATUS 0x20 /* RW: Status register */
#define EDU_REG_IRQ_STATUS 0x24 /* RO: Interrupt status */
#define EDU_REG_IRQ_RAISE 0x60 /* WO: Raise interrupt (OR into status) */
#define EDU_REG_IRQ_ACK 0x64 /* WO: Acknowledge interrupt (clear bits) */
#define EDU_REG_DMA_SRC 0x80 /* RW: DMA source address (guest phys) */
#define EDU_REG_DMA_DST 0x88 /* RW: DMA destination address (guest phys) */
#define EDU_REG_DMA_COUNT 0x90 /* RW: DMA transfer byte count */
#define EDU_REG_DMA_CMD 0x98 /* WO: DMA command / trigger */
/* Status register bits */
#define EDU_STATUS_COMPUTING 0x01 /* Factorial computation in progress */
#define EDU_STATUS_IRQFACT 0x80 /* Raise IRQ on factorial completion */
/* Interrupt status bits */
#define EDU_IRQ_FACT_DONE 0x01 /* Factorial computation completed */
#define EDU_IRQ_DMA_DONE 0x100 /* DMA transfer completed */
/* DMA command bits */
#define EDU_DMA_START 0x01 /* Start DMA transfer */
#define EDU_DMA_FROM_DEV 0x02 /* Direction: device buffer -> RAM */
#define EDU_DMA_TO_DEV 0x00 /* Direction: RAM -> device buffer (default) */
#define EDU_DMA_IRQ 0x04 /* Raise interrupt on DMA completion */
/* Device constraints */
#define EDU_DMA_BUF_SIZE 4096 /* Device internal buffer size */
#endif /* _EDU_REGS_H_ */
/*
* edu_ioctl.h - ioctl interface for the QEMU edu PCI driver
*
* Shared between kernel module and userspace test program.
*/
#ifndef _EDU_IOCTL_H_
#define _EDU_IOCTL_H_
#include <linux/types.h>
#include <linux/ioctl.h>
#define EDU_IOCTL_MAGIC 'E'
/* Read the device identification register */
#define EDU_IOCTL_GET_ID _IOR(EDU_IOCTL_MAGIC, 0, __u32)
/* Liveness check: write val, read back ~val */
#define EDU_IOCTL_ALIVE _IOWR(EDU_IOCTL_MAGIC, 1, __u32)
/* Compute factorial: write n, get n! */
#define EDU_IOCTL_FACTORIAL _IOWR(EDU_IOCTL_MAGIC, 2, __u32)
/* DMA transfer descriptor for ioctl */
struct edu_dma_op {
__u64 addr; /* Userspace buffer address */
__u32 len; /* Transfer length (max 4096) */
__u32 from_dev; /* 0 = write to device, 1 = read from device */
};
/* DMA: transfer data to/from device internal buffer */
#define EDU_IOCTL_DMA _IOWR(EDU_IOCTL_MAGIC, 3, struct edu_dma_op)
/* Raise / acknowledge interrupts manually (for testing) */
#define EDU_IOCTL_IRQ_RAISE _IOW(EDU_IOCTL_MAGIC, 4, __u32)
#endif /* _EDU_IOCTL_H_ */
/*
* edu_drv.c - Linux PCI driver for the QEMU "edu" educational device
*
* Demonstrates:
* - PCI device probe/remove lifecycle
* - BAR0 MMIO register access (ioread32/iowrite32)
* - MSI interrupt handling
* - DMA coherent buffer allocation (dma_alloc_coherent)
* - DMA transfers (RAM <-> device internal buffer)
* - Character device interface (open/release/ioctl)
*
* Target: QEMU edu device (vendor 0x1234, device 0x11e8)
* hw/misc/edu.c / docs/specs/edu.txt
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/mutex.h>
#include "edu_regs.h"
#include "edu_ioctl.h"
#define DRIVER_NAME "edu_pci"
#define EDU_MAX_DEVS 4
/* ── Per-device private data ──────────────────────────────────────────────── */
struct edu_dev {
struct pci_dev *pdev;
void __iomem *bar0; /* MMIO base */
int irq; /* MSI IRQ number */
/* DMA coherent buffer */
void *dma_buf; /* kernel virtual address */
dma_addr_t dma_handle; /* bus/physical address */
/* Synchronisation */
wait_queue_head_t irq_wq; /* wait for IRQ */
u32 irq_status; /* latched IRQ status bits */
struct mutex lock; /* serialise ioctl access */
/* Char device */
struct cdev cdev;
dev_t devno;
int idx; /* minor number index */
};
/* ── Globals ──────────────────────────────────────────────────────────────── */
static dev_t edu_devno_base;
static struct class *edu_class;
static int edu_dev_count;
/* ── Register helpers ─────────────────────────────────────────────────────── */
static inline u32 edu_reg_read(struct edu_dev *edu, u32 offset)
{
return ioread32(edu->bar0 + offset);
}
static inline void edu_reg_write(struct edu_dev *edu, u32 offset, u32 val)
{
iowrite32(val, edu->bar0 + offset);
}
/* DMA address registers (0x80, 0x88) are 64-bit wide */
static inline void edu_reg_write64(struct edu_dev *edu, u32 offset, u64 val)
{
iowrite32((u32)val, edu->bar0 + offset);
iowrite32((u32)(val >> 32), edu->bar0 + offset + 4);
}
/* ── Interrupt handler ────────────────────────────────────────────────────── */
static irqreturn_t edu_irq_handler(int irq, void *data)
{
struct edu_dev *edu = data;
u32 status;
status = edu_reg_read(edu, EDU_REG_IRQ_STATUS);
if (!status)
return IRQ_NONE;
/* Latch the status and acknowledge */
edu->irq_status |= status;
edu_reg_write(edu, EDU_REG_IRQ_ACK, status);
wake_up_interruptible(&edu->irq_wq);
dev_dbg(&edu->pdev->dev, "IRQ: status=0x%x\n", status);
return IRQ_HANDLED;
}
/* ── DMA transfer ─────────────────────────────────────────────────────────── */
/*
* edu_do_dma - perform a DMA transfer between the coherent buffer and the
* edu device's internal 4 KiB buffer.
*
* @edu: device private data
* @len: number of bytes (1..4096)
* @from_dev: 0 = RAM->device, 1 = device->RAM
*
* The edu device DMA engine works as follows:
* - src/dst are guest physical addresses
* - The device has an internal 4 KiB buffer at address 0x40000
* (mapped inside the device, not guest RAM)
* - RAM->device: src = dma_handle (our buffer), dst = 0x40000
* - device->RAM: src = 0x40000, dst = dma_handle (our buffer)
*/
static int edu_do_dma(struct edu_dev *edu, u32 len, int from_dev)
{
u32 cmd;
int ret;
if (len == 0 || len > EDU_DMA_BUF_SIZE)
return -EINVAL;
/* Clear any pending DMA interrupt */
edu->irq_status &= ~EDU_IRQ_DMA_DONE;
if (from_dev) {
/* Device buffer -> RAM */
edu_reg_write64(edu, EDU_REG_DMA_SRC, 0x40000ULL);
edu_reg_write64(edu, EDU_REG_DMA_DST, edu->dma_handle);
cmd = EDU_DMA_START | EDU_DMA_FROM_DEV | EDU_DMA_IRQ;
} else {
/* RAM -> Device buffer */
edu_reg_write64(edu, EDU_REG_DMA_SRC, edu->dma_handle);
edu_reg_write64(edu, EDU_REG_DMA_DST, 0x40000ULL);
cmd = EDU_DMA_START | EDU_DMA_TO_DEV | EDU_DMA_IRQ;
}
edu_reg_write(edu, EDU_REG_DMA_COUNT, len);
edu_reg_write(edu, EDU_REG_DMA_CMD, cmd);
/* Wait for DMA completion interrupt */
ret = wait_event_interruptible_timeout(edu->irq_wq,
edu->irq_status & EDU_IRQ_DMA_DONE,
msecs_to_jiffies(5000));
if (ret == 0)
return -ETIMEDOUT;
if (ret < 0)
return ret;
edu->irq_status &= ~EDU_IRQ_DMA_DONE;
return 0;
}
/* ── Char device operations ───────────────────────────────────────────────── */
static int edu_open(struct inode *inode, struct file *filp)
{
struct edu_dev *edu = container_of(inode->i_cdev, struct edu_dev, cdev);
filp->private_data = edu;
return 0;
}
static int edu_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long edu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct edu_dev *edu = filp->private_data;
void __user *uarg = (void __user *)arg;
u32 val;
int ret = 0;
if (mutex_lock_interruptible(&edu->lock))
return -ERESTARTSYS;
switch (cmd) {
case EDU_IOCTL_GET_ID:
val = edu_reg_read(edu, EDU_REG_ID);
if (copy_to_user(uarg, &val, sizeof(val)))
ret = -EFAULT;
break;
case EDU_IOCTL_ALIVE:
if (copy_from_user(&val, uarg, sizeof(val))) {
ret = -EFAULT;
break;
}
edu_reg_write(edu, EDU_REG_ALIVE, val);
val = edu_reg_read(edu, EDU_REG_ALIVE);
if (copy_to_user(uarg, &val, sizeof(val)))
ret = -EFAULT;
break;
case EDU_IOCTL_FACTORIAL:
if (copy_from_user(&val, uarg, sizeof(val))) {
ret = -EFAULT;
break;
}
/* Arm interrupt on factorial completion */
edu->irq_status &= ~EDU_IRQ_FACT_DONE;
edu_reg_write(edu, EDU_REG_STATUS, EDU_STATUS_IRQFACT);
edu_reg_write(edu, EDU_REG_FACTORIAL, val);
/* Wait for factorial completion */
ret = wait_event_interruptible_timeout(edu->irq_wq,
edu->irq_status & EDU_IRQ_FACT_DONE,
msecs_to_jiffies(5000));
if (ret == 0) {
ret = -ETIMEDOUT;
break;
}
if (ret < 0)
break;
ret = 0;
edu->irq_status &= ~EDU_IRQ_FACT_DONE;
val = edu_reg_read(edu, EDU_REG_FACTORIAL);
if (copy_to_user(uarg, &val, sizeof(val)))
ret = -EFAULT;
break;
case EDU_IOCTL_DMA: {
struct edu_dma_op op;
if (copy_from_user(&op, uarg, sizeof(op))) {
ret = -EFAULT;
break;
}
if (op.len == 0 || op.len > EDU_DMA_BUF_SIZE) {
ret = -EINVAL;
break;
}
if (!op.from_dev) {
/* Userspace -> DMA buf -> device */
if (copy_from_user(edu->dma_buf,
(void __user *)(unsigned long)op.addr,
op.len)) {
ret = -EFAULT;
break;
}
}
ret = edu_do_dma(edu, op.len, op.from_dev);
if (ret)
break;
if (op.from_dev) {
/* Device -> DMA buf -> userspace */
if (copy_to_user(
(void __user *)(unsigned long)op.addr,
edu->dma_buf, op.len)) {
ret = -EFAULT;
break;
}
}
break;
}
case EDU_IOCTL_IRQ_RAISE:
if (copy_from_user(&val, uarg, sizeof(val))) {
ret = -EFAULT;
break;
}
edu_reg_write(edu, EDU_REG_IRQ_RAISE, val);
break;
default:
ret = -ENOTTY;
}
mutex_unlock(&edu->lock);
return ret;
}
static const struct file_operations edu_fops = {
.owner = THIS_MODULE,
.open = edu_open,
.release = edu_release,
.unlocked_ioctl = edu_ioctl,
};
/* ── PCI probe / remove ───────────────────────────────────────────────────── */
static int edu_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct edu_dev *edu;
struct device *dev_node;
int ret;
u32 reg_id;
edu = devm_kzalloc(&pdev->dev, sizeof(*edu), GFP_KERNEL);
if (!edu)
return -ENOMEM;
edu->pdev = pdev;
edu->idx = edu_dev_count++;
mutex_init(&edu->lock);
init_waitqueue_head(&edu->irq_wq);
pci_set_drvdata(pdev, edu);
/* Step 1: Enable the PCI device */
ret = pcim_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "Failed to enable PCI device\n");
return ret;
}
/* Step 2: Request MMIO regions */
ret = pcim_iomap_regions(pdev, BIT(0), DRIVER_NAME);
if (ret) {
dev_err(&pdev->dev, "Failed to request BAR0 region\n");
return ret;
}
edu->bar0 = pcim_iomap_table(pdev)[0];
if (!edu->bar0) {
dev_err(&pdev->dev, "Failed to map BAR0\n");
return -ENOMEM;
}
/* Verify device identity */
reg_id = edu_reg_read(edu, EDU_REG_ID);
dev_info(&pdev->dev, "EDU device ID register: 0x%08x\n", reg_id);
/* Step 3: Set DMA mask and enable bus mastering */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
/* Some IOMMU configurations require wider masks */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
dev_err(&pdev->dev, "Failed to set DMA mask\n");
return ret;
}
}
pci_set_master(pdev);
/* Step 4: Allocate DMA coherent buffer */
edu->dma_buf = dma_alloc_coherent(&pdev->dev, EDU_DMA_BUF_SIZE,
&edu->dma_handle, GFP_KERNEL);
if (!edu->dma_buf) {
dev_err(&pdev->dev, "Failed to allocate DMA buffer\n");
return -ENOMEM;
}
dev_info(&pdev->dev, "DMA buffer: virt=%px phys=0x%llx\n",
edu->dma_buf, (unsigned long long)edu->dma_handle);
/* Step 5: Set up MSI interrupt */
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate IRQ vectors\n");
goto err_free_dma;
}
edu->irq = pci_irq_vector(pdev, 0);
ret = devm_request_irq(&pdev->dev, edu->irq, edu_irq_handler,
0, DRIVER_NAME, edu);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", edu->irq);
goto err_free_irq_vectors;
}
dev_info(&pdev->dev, "IRQ %d registered (MSI)\n", edu->irq);
/* Step 6: Create character device */
edu->devno = MKDEV(MAJOR(edu_devno_base), edu->idx);
cdev_init(&edu->cdev, &edu_fops);
edu->cdev.owner = THIS_MODULE;
ret = cdev_add(&edu->cdev, edu->devno, 1);
if (ret) {
dev_err(&pdev->dev, "Failed to add cdev\n");
goto err_free_irq_vectors;
}
dev_node = device_create(edu_class, &pdev->dev, edu->devno,
NULL, "edu%d", edu->idx);
if (IS_ERR(dev_node)) {
ret = PTR_ERR(dev_node);
dev_err(&pdev->dev, "Failed to create device node\n");
goto err_cdev_del;
}
dev_info(&pdev->dev,
"edu%d: QEMU EDU device ready (BAR0=%pR, IRQ=%d, DMA@0x%llx)\n",
edu->idx, &pdev->resource[0], edu->irq,
(unsigned long long)edu->dma_handle);
return 0;
err_cdev_del:
cdev_del(&edu->cdev);
err_free_irq_vectors:
pci_free_irq_vectors(pdev);
err_free_dma:
dma_free_coherent(&pdev->dev, EDU_DMA_BUF_SIZE,
edu->dma_buf, edu->dma_handle);
return ret;
}
static void edu_remove(struct pci_dev *pdev)
{
struct edu_dev *edu = pci_get_drvdata(pdev);
device_destroy(edu_class, edu->devno);
cdev_del(&edu->cdev);
pci_free_irq_vectors(pdev);
dma_free_coherent(&pdev->dev, EDU_DMA_BUF_SIZE,
edu->dma_buf, edu->dma_handle);
dev_info(&pdev->dev, "edu%d: removed\n", edu->idx);
}
/* ── PCI ID table ─────────────────────────────────────────────────────────── */
static const struct pci_device_id edu_pci_ids[] = {
{ PCI_DEVICE(EDU_VENDOR_ID, EDU_DEVICE_ID) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, edu_pci_ids);
static struct pci_driver edu_pci_driver = {
.name = DRIVER_NAME,
.id_table = edu_pci_ids,
.probe = edu_probe,
.remove = edu_remove,
};
/* ── Module init / exit ───────────────────────────────────────────────────── */
static int __init edu_init(void)
{
int ret;
ret = alloc_chrdev_region(&edu_devno_base, 0, EDU_MAX_DEVS, DRIVER_NAME);
if (ret) {
pr_err(DRIVER_NAME ": Failed to allocate chrdev region\n");
return ret;
}
edu_class = class_create(DRIVER_NAME);
if (IS_ERR(edu_class)) {
ret = PTR_ERR(edu_class);
pr_err(DRIVER_NAME ": Failed to create device class\n");
goto err_unreg_chrdev;
}
ret = pci_register_driver(&edu_pci_driver);
if (ret) {
pr_err(DRIVER_NAME ": Failed to register PCI driver\n");
goto err_class_destroy;
}
pr_info(DRIVER_NAME ": driver loaded\n");
return 0;
err_class_destroy:
class_destroy(edu_class);
err_unreg_chrdev:
unregister_chrdev_region(edu_devno_base, EDU_MAX_DEVS);
return ret;
}
static void __exit edu_exit(void)
{
pci_unregister_driver(&edu_pci_driver);
class_destroy(edu_class);
unregister_chrdev_region(edu_devno_base, EDU_MAX_DEVS);
pr_info(DRIVER_NAME ": driver unloaded\n");
}
module_init(edu_init);
module_exit(edu_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("PCIe/DMA Development");
MODULE_DESCRIPTION("PCI driver for QEMU edu device with DMA support");
MODULE_VERSION("1.0");
