一个最简单的LINUX-PCIE设备驱动
+ -

LINUX-PCIE驱动hello_pcie源代码

2024-09-14 1 0

一、前言

教程参考:02 实战部分 PCIE设备测试https://www.bilibili.com/video/BV1zx4y1Y7D2
教程参考:03 PCIe设备驱动源码解析https://www.bilibili.com/video/BV1KT421m7Gk

二、驱动编写

新建hello_pcie.c文件

touch hello_pcie.c

然后编写内容如下所示:

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

#define HELLO_PCI_DEVICE_ID        0x11e8
#define HELLO_PCI_VENDOR_ID        0x1234
#define HELLO_PCI_REVISION_ID    0x10

static struct pci_device_id ids[] = {
    { PCI_DEVICE(HELLO_PCI_VENDOR_ID, HELLO_PCI_DEVICE_ID), },
    { 0 , }
};

static struct hello_pci_info_t {
    struct pci_dev *dev;
    void __iomem *address_bar0;
} hello_pci_info;

MODULE_DEVICE_TABLE(pci, ids);

static irqreturn_t hello_pci_irq_handler(int irq, void *dev_info)
{
    struct hello_pci_info_t *_pci_info = dev_info;
    uint32_t irq_status;

    // get irq_stutas
    irq_status = *((uint32_t *)(_pci_info->address_bar0 + 0x24));
    printk("hello_pcie: get irq status: 0x%0x\n", irq_status);
    // clean irq
    *((uint32_t *)(_pci_info->address_bar0 + 0x64)) = irq_status;

    // get irq_stutas
    irq_status = *((uint32_t *)(_pci_info->address_bar0 + 0x24));
    if(irq_status == 0x00){
        printk("hello_pcie: receive irq and clean success. \n");
        return IRQ_HANDLED;
    }else{
        printk("hello_pcie: receive irq but clean failed !!! \n");
        return IRQ_NONE;
    }
}

static int hello_pcie_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
    int bar = 0;
    int ret;
    resource_size_t len;

    ret = pci_enable_device(dev);
    if(ret) {
        return ret;
    }

    len = pci_resource_len(dev, bar);
    hello_pci_info.address_bar0 = pci_iomap(dev, bar, len);
    hello_pci_info.dev = dev;

    // register interrupt
    ret = request_irq(dev->irq, hello_pci_irq_handler, IRQF_SHARED, "hello_pci", &hello_pci_info);
    if(ret) {
        printk("request IRQ failed.\n");
        return ret;
    }
    // enable irq for finishing factorial computation
    *((uint32_t *)(hello_pci_info.address_bar0 + 0x20)) = 0x80;

    return 0;
}

static void hello_pcie_remove(struct pci_dev *dev)
{
    // disable irq for finishing factorial computation
    *((uint32_t *)(hello_pci_info.address_bar0 + 0x20)) = 0x01;

    free_irq(dev->irq, &hello_pci_info);

    pci_iounmap(dev, hello_pci_info.address_bar0);

    pci_disable_device(dev);
}

static struct pci_driver hello_pci_driver = {
    .name        = "hello_pcie",
    .id_table    = ids,
    .probe        = hello_pcie_probe,
    .remove        = hello_pcie_remove,
};

static int __init hello_pci_init(void)
{
    return pci_register_driver(&hello_pci_driver);
}

static void __exit hello_pci_exit(void)
{
    pci_unregister_driver(&hello_pci_driver);
}

MODULE_LICENSE("GPL");
module_init(hello_pci_init);
module_exit(hello_pci_exit);

三、驱动编译
新建Makefile文件编写内容如下:

ifeq ($(KERNELRELEASE),)

KERNELDIR ?= /lib/modules/$(shell uname -r)/build  
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 

clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules*

.PHONY: all clean

else
  obj-m := hello_pcie.o
endif

然后执行 make命令进行编译
144539978977
编译完成后可以得到驱动对应的 .ko文件

144607765639

四、驱动加载及测试

驱动编译完成后使用如下命令加载即可:

sudo insmod hello_pcie.ko

然后使用lspci查看该pcie设备,可以看到驱动加载成功:
144650896264

同时我们也可以看到其BAR0基地址为0xfea00000,我们使用devmem向其0x08编译地址写入数据进行阶乘运算:
1447185440
使用详细说明可以查看查看qemu源码的docs/specs/edu.txt文件

https://github.com/qemu/qemu/blob/v2.7.0/docs/specs/edu.txt

EDU device
==========

Copyright (c) 2014-2015 Jiri Slaby

This document is licensed under the GPLv2 (or later).

This is an educational device for writing (kernel) drivers. Its original
intention was to support the Linux kernel lectures taught at the Masaryk
University. Students are given this virtual device and are expected to write a
driver with I/Os, IRQs, DMAs and such.

The devices behaves very similar to the PCI bridge present in the COMBO6 cards
developed under the Liberouter wings. Both PCI device ID and PCI space is
inherited from that device.

Command line switches:
    -device edu[,dma_mask=mask]

    dma_mask makes the virtual device work with DMA addresses with the given
    mask. For educational purposes, the device supports only 28 bits (256 MiB)
    by default. Students shall set dma_mask for the device in the OS driver
    properly.

PCI specs
---------

PCI ID: 1234:11e8

PCI Region 0:
   I/O memory, 1 MB in size. Users are supposed to communicate with the card
   through this memory.

MMIO area spec
--------------

Only size == 4 accesses are allowed for addresses < 0x80. size == 4 or
size == 8 for the rest.

0x00 (RO) : identification (0xRRrr00edu)
        RR -- major version
        rr -- minor version

0x04 (RW) : card liveness check
        It is a simple value inversion (~ C operator).

0x08 (RW) : factorial computation
        The stored value is taken and factorial of it is put back here.
        This happens only after factorial bit in the status register (0x20
        below) is cleared.

0x20 (RW) : status register, bitwise OR
        0x01 -- computing factorial (RO)
        0x80 -- raise interrupt 0x01 after finishing factorial computation

0x24 (RO) : interrupt status register
        It contains values which raised the interrupt (see interrupt raise
        register below).

0x60 (WO) : interrupt raise register
        Raise an interrupt. The value will be put to the interrupt status
        register (using bitwise OR).

0x64 (WO) : interrupt acknowledge register
        Clear an interrupt. The value will be cleared from the interrupt
        status register. This needs to be done from the ISR to stop
        generating interrupts.

0x80 (RW) : DMA source address
        Where to perform the DMA from.

0x88 (RW) : DMA destination address
        Where to perform the DMA to.

0x90 (RW) : DMA transfer count
        The size of the area to perform the DMA on.

0x98 (RW) : DMA command register, bitwise OR
        0x01 -- start transfer
        0x02 -- direction (0: from RAM to EDU, 1: from EDU to RAM)
        0x04 -- raise interrupt 0x100 after finishing the DMA

IRQ controller
--------------
An IRQ is generated when written to the interrupt raise register. The value
appears in interrupt status register when the interrupt is raised and has to
be written to the interrupt acknowledge register to lower it.

DMA controller
--------------
One has to specify, source, destination, size, and start the transfer. One
4096 bytes long buffer at offset 0x40000 is available in the EDU device. I.e.
one can perform DMA to/from this space when programmed properly.

Example of transferring a 100 byte block to and from the buffer using a given
PCI address 'addr':
addr     -> DMA source address
0x40000  -> DMA destination address
100      -> DMA transfer count
1        -> DMA command register
while (DMA command register & 1)
    ;

0x40000  -> DMA source address
addr+100 -> DMA destination address
100      -> DMA transfer count
3        -> DMA command register
while (DMA command register & 1)
    ;

然后我们使用dmesg命令可以查看驱动的相关打印:
144824449312

0 篇笔记 写笔记

关注公众号
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!