您的位置:首页 > 运维架构 > Linux

linux驱动学习记录(三)-PCI IO读写、中断、DMA传输

2017-10-08 16:28 661 查看
实现方法不止本文这些,本文只是作者对自己成功实现的方法记录

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将中断处理程序分解为两个半部:顶半部(Top Half)和底半部(Bottom Half)。



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
a08d
_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);

 

最后将总线地址写入到硬件寄存器中,具体逻辑实现须和硬件工程师进行沟通。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐