本文共 3026 字,大约阅读时间需要 10 分钟。
实现方法不止本文这些,本文只是作者对自己成功实现的方法记录
1. PCI IO内存读写
I/O端口是驱动程序与许多设备之间的通信方式,Linux的内核为我们提供了I/O端口分配的操作接口,但对PCI设备来讲,它的配置地址空间已经为其指定了I/O端口范围,不需要额外的分配操作。
下列代码通过访问I/O内存实现访问设备内存。
unsigned long mmio_start, addr1, addr2; void __iomem *ioaddr; mmio_start = pci_resource_start( pdev, 1);//获取bar1的首地址ioaddr = pci_iomap(pdev, 1, 0); //内核对PCI bar1 内存映射的地址 addr1 = ioread32( ioaddr ); addr2 = ioread32( ioaddr + 4 ); printk(KERN_INFO "mmio start: %lX\n", mmio_start); printk(KERN_INFO "ioaddr: %p\n", ioaddr); |
2. 中断
2.1 申请中断与释放中断
在设备文件打开(即进入file_operationsopen 成员函数)时,使用request_irq函数向内核申请中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev); |
irq是要申请的硬件中断号。
handler是向系统登记的中断处理函数(顶半部),是一个回调函数,中断发生时,系统调用这个函数,dev参数将被传递给它。
irqflags是中断处理的属性,可以指定中断的触发方式以及处理方式。在触发方式方面,可以是IRQF_TRIGGER_RISING、 IRQF_TRIGGER_FALLING、 IRQF_TRIGGER_HIGH、 IRQF_TRIGGER_LOW等。在处理方式方面,若设置了IRQF_SHARED,则表示多个设备共享中断,dev是要传递给中断服务程序的私有数据,一般设置为这个设备的设备结构体或者NULL。
在设备文件关闭(即进入file_operationsrelease 成员函数)时,使用free_irq函数释放中断
void free_irq(unsigned int irq,void *dev_id); |
2.2 linux中断处理机制
Linux实现底半部的机制主要有tasklet、工作队列、软中断和线程化irq。
进入中断服务程序后有三步操作
1. 根据硬件协议读取中断状态位,利用掩码判断是否为本设备的中断,如果不是本设备中断则返回IRQ_NONE。 2. 清除中断标志。 3. 完成中断要处理的工作(如果工作量大,应该将耗时的工作交给底半部工作队列,尽快退出中断服务程序),返回IRQ_HANDLED,告知内核该中断已被处理。
|
下列为使用tasklet作为底半部处理中断的示例代码
/*中断处理底半部*/void xxx_do_tasklet(unsigned long){ ...}/* 定义tasklet和底半部函数并将它们关联 */DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);/*中断处理顶半部*/irqreturn_t xxx_interrupt(int irq, void *dev_id){ //读取状态位 unsigned long int_status = ioread32(bar_base[0] + 8); //利用掩码判断是否为本设备的中断 if(!(int_status & 0x3)) { return IRQ_NONE; } //清除中断标志 iowrite32(0xff00, bar_base[0] + 0); //调度tasklet tasklet_schedule(&xxx_tasklet); return IRQ_HANDLED;}/* 字符设备file_operations open 成员函数 */static int xxx_open(struct inode *inode, struct file *filp){ ... //申请中断,注册中断服务程序 if(request_irq(irq, xxx_interrupt, IRQF_SHARED, PCI_NAME, lspci_devp)) { printk("==========>>request_irq error\n"); } ...}/* 字符设备file_operations release 成员函数 */static int xxx_release(struct inode *inode, struct file *filp){ ... //释放中断 free_irq(irq, lspci_devp); ...}
3. DMA传输
DMA本身不属于一种等同于字符设备、块设备和网络设备的外设,它只是一种外设与内存交互数据的方式。
驱动要做的是在内核空间中申请内存,往内存中写入数据(或者是等DMA传输结束后读出数据,取决于数据传输的方向),将这块内存映射为物理地址,把地址发送给设备并告知设备DMA传输准备工作已完成。
内核空间中申请内存可以使用kmalloc()函数
void *kmalloc(size_t size, int flags); |
给kmalloc()的第一个参数是要分配的块的大小;第二个参数为分配标志,用于控制kmalloc()的行为。最常用的分配标志是GFP_KERNEL,其含义是在内核空间的进程中申请内存。使用kfree()函数将内存释放。
对于单个已经分配的缓冲区而言,使用dma_map_single()可实现流式DMA映射,该函数原型为:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction); |
如果映射成功,返回的是总线地址,否则,返回NULL。第4个参数为DMA的方向,可能的值包括
DMA_TO_DEVICE、DMA_FROM_DEVICE、DMA_BIDIRECTIONAL和DMA_NONE。
dma_map_single()的反函数为dma_unmap_single(),原型是:
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction); |
最后将总线地址写入到硬件寄存器中,具体逻辑实现须和硬件工程师进行沟通。
转载地址:https://blog.csdn.net/qq_22042587/article/details/78175648 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!