PCI驱动程序

版权声明:没有技术的的技术宅 https://blog.csdn.net/qq_23084801/article/details/82495493

本文参考:https://blog.csdn.net/cjecho/article/details/54934264

PCI(Peripheral Component Interconnect)外围设备互联是一组完整的规范,定义了计算机各个不同部分之间应该如何交互。每个PCI外设由一个总线编号、一个设备编号、一个功能编号来标识。Linux支持PCI域,每个域可以拥有256个总线,每个总线 可以支持32个设备,每个设备都可以是多功能版(如CD-ROM驱动器),最多可有8种功能。所以每种功能可以在硬件级由一个16位地址或键来标识。单个系统中插入多个总线可以通过桥完成,它是用来连接两个总线的特殊PCI外设。

lspci
会列出/prco/bus/pci/devices下的设备或/sys/bus/pci/devices

每个外设的硬件电路对如下三种地址空间的查询进行应答:内存位置、I/O端口、配置寄存器。前两种地址空间由同一条PCI总线的所有设备共享。对驱动程序而言,内存和IO区域通过inb和readb等进行访问。配置事物通过调用特定内核函数访问配置寄存器来执行。固件在系统引导时初始化PCI硬件,把每个区域映射到不同的地址,这些区域映射的地址可以从配置空间读取,因此Linux驱动程序不需要探测就能访问设备。在读取配置寄存器后,驱动程序就可以安全访问其硬件。每个设备版是通过物理地址来获取其配置寄存器的。所有PCI设备 至少有256字节的地址空间。

pci_device_id被用在struct pci_driver (它告诉用户空间这个特定的驱动程序支持哪些设备)中。在示例中,创建了一个结构体数组,每一个结构表明使用该结构体数组的驱动支持的设备,数组的最后一个值是全部设置为0的空结构体,也就是{0,}。这个结构体需要被导出到用户空间,使热插拔和模块装载系统知道什么模块对应什么硬件设备,宏MODULE_DEVICE_TABLE完成这个工作。pci_dev这个数据结构也在文件include/linux/pci.h里,它详细描述了一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等

struct pci_device_id {
       __u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
       __u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
       __u32 class, class_mask; /* (class,subclass,prog-if) triplet */
       kernel_ulong_t driver_data; /* Data private to the driver */
};

使用宏PCI_DEVICE对pci_device_id 结构进行初始化
static struct pci_device_id demo_pci_tbl[] ={
    {PCI_DEVICE(PCI_VENDOR_ID_DEMO,PCI_DEVICE_ID_DEMO),},
    {0,},
};

MODULE_DEVICE_TABLE(pci, demo_pci_tbl);

当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有PCI设备的拓扑结构。

系统加载模块是调用pci_init_module函数,在这个函数中我们通过pci_register_driver 把new_pci_driver注册到系统中。在调用pci_register_driver时,需要提供一个pci_driver结构。这个函数首先检测id_table中定义的PCI信息是否和系统中的PCI信息有匹配,如果有则返回0,匹配成功后调用probe函数对PCI设备进行进一步的操作。probe函数的作用就是启动pci设备,读取配置空间信息,进行相应的初始化。

/* 设备模块信息 */
static struct pci_driver demo_pci_driver = {
    .name= DEMO_MODULE_NAME,  /* 设备模块名称 */
    .id_table = demo_pci_tbl, /* 能够驱动的设备列表 */
    .probe = demo_probe,      /* 查找并初始化设备 */
    .remove =  demo_remove,   /* 卸载设备模块 */
/* ... */};

static int __init  demo_init_module (void)
{
pci_register_driver(&demo_pci_driver); //注册设备驱动
/* ... */}


static int __init demo_probe(struct pci_dev *pci_dev,const struct pci_device_id *pci_id)
{
    int result;
    printk("probe function is running\n");
    struct device_privdata *privdata;
    privdata->pci_dev = pci_dev;     

    //把设备指针地址放入PCI设备中的设备指针中,便于后面调用pci_get_drvdata
    pci_set_drvdata(pci_dev, privdata); 

    /* 启动PCI设备 */
    if(pci_enable_device(pci_dev))
    {
        printk(KERN_ERR "%s:cannot enable device\n", pci_name(pci_dev)); 
        return -ENODEV;
    } 

    /*动态申请设备号,把fops传进去*/
    privdata->cdev = cdev_alloc();
    privdata->cdev->ops=&jlas_fops;
    privdata->cdev->owner = THIS_MODULE;
    cdev_add(privdata->cdev,devno,1);

    /*动态创建设备节点*/
    privdata->cdev_class = class_create(THIS_MODULE,DEV_NAME);
    device_create(privdata->cdev_class,NULL, devno, pci_dev, DEV_NAME);
    privdata->irq=pci_dev->irq;
    privdata->iobase=pci_resource_start(privdata->pci_dev, BAR_IO);

    /*判断IO资源是否可用*/
    if((pci_resource_flags(pci_dev, BAR_IO) & IORESOURCE_IO) != IORESOURCE_IO)
        goto err_out;

    /* 对PCI区进行标记 ,标记该区域已经分配出去*/ 
    ret= pci_request_regions(pci_dev, DEVICE_NAME);
    if(ret) 
         goto err_out;

    /*初始化tasklet*/
    tasklet_init(&(privdata->my_tasklet),jlas_1780_do_tasklet,(unsigned long )&jlas_pci_cdev); 

    /* 初始化自旋锁 */     
    spin_lock_init(&private->lock); 

    /*初始化等待队列*/
    init_waitqueue_head(&(privdata->read_queue)); 

    /* 设置成总线主DMA模式 */ 
    pci_set_master(pci_dev); 

    /*申请内存*/
    privdata->mem = (u32 *) __get_free_pages(GFP_KERNEL|__GFP_DMA | __GFP_ZERO, memorder);
    if (!privdata->mem) {   
        goto err_out;
    } 

    /*DMA映射*/
    privdata->dma_addrp = pci_map_single(pdev, privdata->mem,PAGE_SIZE * (1 << memorder), PCI_DMA_FROMDEVICE);
    if (pci_dma_mapping_error(pdev, privdata->dma_addrp)) {
        goto err_out;
    }  

    /*对硬件进行初始化设置,往寄存器中写一些值,复位硬件等*/
    device_init(xx_device);
    return 0;

err_out:
    printk("error process\n");
    resource_cleanup_dev(FCswitch); //如果出现任何问题,释放已经分配了的资源
        return ret;
}


中断处理:在Linux引导阶段,计算机固件就为设备分配了一个唯一的中断号。中断处理,主要就是读取中断寄存器,然后调用中断处理函数来处理中断的下半部分,一般通过tasklet或者workqueue来实现。由于使用request_irq 获得的中断是共享中断,因此在中断处理函数的上半部需要区分是不是该设备发出的中断,这就需要读取中断状态寄存器的值来判断,如果不是该设备发起的中断则 返回 IRQ_NONE。

/* 中断处理模块 */
void jlas_do_tasklet(unsigned long data)
{
    spin_lock(&(privdata->my_spin_lock));
    //具体操作
    spin_unlock(&(privdata->my_spin_lock));
    wake_up_interruptible(&(privdata->read_queue));
}
static irqreturn_t device_interrupt(int irq, void *dev_id)
{
    struct device_privdata *privdata = dev_id;
    tasklet_schedule(&(privdata->my_tasklet));
    return IRQ_HANDLED;
    /* ... */}


//probe中调用的函数原型
void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}

设备文件操作接口 
当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。device_init(xx_device);在probe中调用。

/* 设备文件操作接口 */
static struct file_operations demo_fops={
    .owner = THIS_MODULE, /* demo_fops所属的设备模块 */
    .read = demo_read,    /* 读设备操作*/
    .write = demo_write,  /* 写设备操作*/
    .open = demo_open,    /* 打开设备操作*//  
    .ioctl = demo_ioctl,  /* 控制设备操作*/
    .mmap = demo_mmap,    /* 内存重映射操作*/
    .release = demo_release, /* 释放设备操作*/
    /* ... */}; 

PCI设备私有数据结构

/* 对特定PCI设备进行描述的数据结构 */
struct device_private
{
    /*次设备号*/
    unsigned int minor; 
    /*注册字符驱动和发现PCI设备的时候使用*/
    struct pci_dev *pci_dev;
    struct cdev *cdev;
    struct class *cdev_class;
    /*中断号*/ 
    unsigned int irq;
    /* 用于获取PCI设备配置空间的基本信息 */   
    unsigned long iobase;
    /*用于保存分配给PCI设备的内存空间的信息*/     
    dma_addr_t dma_addrp;
    char *virts_addr;
    /*基本的同步手段*/
    spinlock_t lock;
    /*等待队列*/
    wait_queue_head_t read_queue;
    /*tasklet*/
    struct tasklet_struct my_tasklet;
    /*异步*/
    struct fasync_struct *async_queue;
    /*设备打开标记*/
    int open_flag /
    /* .....*/
};

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_23084801/article/details/82495493