QEMU EDU 设备驱动

QEMU EDU 设备驱动

QEMU EDU 设备是 QEMU 中用于设备驱动程序教学的设备。在马萨里克大学的 Linux 内核课程中,学生可以使用这个虚拟设备编写一个包含 I/O、IRQ、DMA 等的驱动程序。

设备规范:EDU device — QEMU documentation

设备源码:https://github.com/qemu/qemu/blob/master/hw/misc/edu.c

本文参考已有的项目重新从零实现 QEMU EDU 的驱动程序。参考项目:

由于文章中的代码是由浅入深,不断叠加修改,因此若文中有不合理或疏忽的地方请参考最终的源码。

源码地址:https://github.com/jklincn/qemu_edu_driver

QEMU 启动

本文使用的 QEMU 版本为 8.2.5,QEMU 的安装以及磁盘镜像的准备可以参考[在 WSL 中使用 QEMU 搭建 PCIe 模拟环境]。

qemu-system-x86_64 -enable-kvm \
        -M q35 \
        -cpu SapphireRapids-v2 \
        -smp 8 \
        -m 16G \
        -hda ubuntu.qcow2 \
        -netdev user,id=net0,hostfwd=tcp::10022-:22 \
        -device e1000,netdev=net0 \
        -device edu

此处对网络设备的设置是为了可以使用 vscode 进行远程连接,方便开发。

进入系统后使用 lspci 应该可以看到 edu 设备,即 00:03.0 Unclassified device [00ff]: Device 1234:11e8 (rev 10)

$ lspci
00:00.0 Host bridge: Intel Corporation 82G33/G31/P35/P31 Express DRAM Controller
00:01.0 VGA compatible controller: Device 1234:1111 (rev 02)
00:02.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)
00:03.0 Unclassified device [00ff]: Device 1234:11e8 (rev 10)
00:1f.0 ISA bridge: Intel Corporation 82801IB (ICH9) LPC Interface Controller (rev 02)
00:1f.2 SATA controller: Intel Corporation 82801IR/IO/IH (ICH9R/DO/DH) 6 port SATA Controller [AHCI mode] (rev 02)
00:1f.3 SMBus: Intel Corporation 82801I (ICH9 Family) SMBus Controller (rev 02)

编写 PCI 驱动程序

这一部分可以先阅读 Linux 官方文档:1. How To Write Linux PCI Drivers — The Linux Kernel documentation

简单驱动模板

先搭一个大体的框架,这和 PCI 无关,这是为了测试当前的环境配置,该文件命名为 qemu_edu_driver.c

#include <linux/module.h>

#define DRIVER_NAME "qemu_edu"

static int edu_init() {
   
    printk(KERN_INFO "[%s] Init sucessful. \n", DRIVER_NAME);
    return 0;
}

static void edu_exit() {
   }

module_init(edu_init);
module_exit(edu_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("QEMU EDU Device Driver");

再创建一个 Makefile

obj-m	:= qemu_edu_driver.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
CFLAGS=-Wall

modules:
	make -C $(KERNELDIR) M=$(PWD) modules

clean:
	make -C $(KERNELDIR) M=$(PWD) clean

.PHONY: modules clean

安装编译工具与内核头文件

sudo apt install build-essential linux-headers-$(uname -r)

进行编译和加载

make
sudo insmod qemu_edu_driver.ko
sudo dmesg | grep qemu_edu

如果一切正常,可以看到有 [qemu_edu] Init sucessful. 这样的输出

注册驱动程序

这里主要涉及到 pci_register_driver() 函数,其接口是

/* Proper probing supporting hot-pluggable devices */
int __must_check __pci_register_driver(struct pci_driver *, struct module *,
				       const char *mod_name);

/* pci_register_driver() must be a macro so KBUILD_MODNAME can be expanded */
#define pci_register_driver(driver)		\
	__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

因此我们只需要准备好 pci_driver 结构体

static struct pci_driver pci_driver = {
   
    .name = DRIVER_NAME,
    .id_table = pci_ids,
    .probe = edu_probe,
    .remove = edu_remove,
};

其中还涉及到 pci_device_id 结构体,我们把 EDU 设备的信息填充进去,并使用 MODULE_DEVICE_TABLE 进行导出。

static struct pci_device_id pci_ids[] = {
   {
   PCI_DEVICE(0x1234, 0x11e8)},
                                         {
   
                                             0,
                                         }};

这里对于 probe 和 remove 的处理我们先定义两个空函数,其声明可见 pci_driver 结构体。

static int edu_probe(struct pci_dev *pdev, const struct pci_device_id *id) {
   
    return 0;
}

static void edu_remove(struct pci_dev *pdev) {
   }

从 __pci_register_driver() 的描述中可以看到其返回值定义:如果有错误发生则返回负数的错误码,否则返回 0,因此做一个判断检查函数是否执行成功。

目前完整的代码如下

#include <linux/module.h>
#include <linux/pci.h>

#define DRIVER_NAME "qemu_edu"

static struct pci_device_id pci_ids[] = {
   {
   PCI_DEVICE(0x1234, 0x11e8)},
                                         {
   
                                             0,
                                         }};
MODULE_DEVICE_TABLE(pci, pci_ids);

static int edu_probe(struct pci_dev *pdev, const struct pci_device_id *id) {
   
    return 0;
}

static void edu_remove(struct pci_dev *pdev) {
   }

static struct pci_driver pci_driver = {
   
    .name = DRIVER_NAME,
    .id_table = pci_ids,
    .probe = edu_probe,
    .remove = edu_remove,
};

static int __init edu_init(void) {
   
    int ret;
    if ((ret = pci_register_driver(&pci_driver)) < 0) {
   
        printk(KERN_INFO "[%s] Init failed. \n", DRIVER_NAME);
        return ret;
    }

    printk(KERN_INFO "[%s] Init sucessful. \n", DRIVER_NAME);
    return ret;
}

static void __exit edu_exit(void) {
   
    pci_unregister_driver(&pci_driver);
    printk(KERN_INFO "[%s] exit sucessful. \n", DRIVER_NAME);
}

module_init(edu_init);
module_exit(edu_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("QEMU EDU Device Driver");

实现 probe 和 remove 函数

根据文档,probe 函数主要做的事情包括:

  1. 启用设备
  2. 请求 MMIO/IOP 资源
  3. 设置 DMA 掩码大小(包括一致性 DMA 和流式 DMA)
  4. 分配和初始化共享控制数据(pci_allocate_coherent())
  5. 访问设备配置空间(如果需要)
  6. 注册 IRQ 处理程序(request_irq()
  7. 初始化非 PCI(即芯片的 LAN/SCSI/ 等部分)
  8. 启用 DMA/处理 引擎

我们根据这个顺序来依次实现它:

void __iomem *mmio_base;
static int edu_probe(struct pci_dev *pdev, const struct pci_device_id *id) {
   
    int ret;

    // Enable the PCI device
    if ((ret = pci_enable_device(pdev)) < 0) {
   
        printk(KERN_ERR "[%s] pci_enable_device failed. \n", DRIVER_NAME);
        return ret;
    }

    // Request MMIO/IOP resources
    if ((ret = pci_request_region(pdev, BAR, DRIVER_NAME)) < 0) {
   
        printk(KERN_ERR "[%s] pci_request_region failed. \n", DRIVER_NAME);
        goto disable_device;
    }

    // Set the DMA mask size
    // EDU device supports only 28 bits by default
    if ((ret = dma_set_mask_and_coherent(&(pdev->dev), DMA_BIT_MASK(28)) < 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值