首页 理论教育PCIeDMA读写操作

PCIeDMA读写操作

【摘要】:源代码12-6 Capric卡的DMA写片段1这段源代码首先对count字段进行检查,因为Capric卡规定一次DMA操作所传递的数据不超过2KB,之后使用kmalloc函数分配DMA写使用的数据缓存。源代码12-7 Capric卡的DMA写片段2 #ifdef和#endif出现在函数体中并不可取,请读者参阅Greg Kroah-Hartman的Proper Linux Kernel Coding Style掌握正确的处理方法。但是这种方法将产生较长的中断延时,从而极大影响Capric卡的DMA读写效率。Capric卡DMA读的硬件操作流程如第12.1.4节所示。

Capric卡的DMA读/写过程与capric_write/capric_read函数对应。

1.DMA写的操作流程

Capric卡的数据传送方法较为简单,其DMA读写的硬件操作流程如第12.1.3节所示。DMA写的实现过程与capric_read函数对应,如源代码12-6~7所示。

源代码12-6 Capric卡的DMA写片段1

这段源代码首先对count字段进行检查,因为Capric卡规定一次DMA操作所传递的数据不超过2KB,之后使用kmalloc函数分配DMA写使用的数据缓存。kmalloc函数所能分配的内存大小受限于Linux系统中的SLAB/SLUB内存分配器,在系统内存紧张时,有可能失败,因此在此必须进行参数检查。值得注意的是,在一个实际驱动程序中,很少在读写服务例程中使用kmalloc函数申请内存,然后使用kfree函数释放这段内存,因为这样做容易产生内存碎片。而且kmalloc函数的执行时间也相对较长,影响数据传送的效率

随后这段代码调用pci_map_single函数将存储器域的虚拟地址virt_addr转换为PCI总线域的物理地址dma_write_addr,供Capric卡的DMA控制器使用。Linux系统提供了一组将虚拟地址转换为设备域物理地址的方法,参见第12.3.5节。

源代码12-7 Capric卡的DMA写片段2

㊀ #ifdef和#endif出现在函数体中并不可取,请读者参阅Greg Kroah-Hartman的Proper Linux Kernel Coding Style掌握正确的处理方法。

如果当前DMA写操作不与Cache进行一致性操作,将首先执行dma_sync_single函数进行存储器与Cache的同步操作,该函数的详细说明见第12.3.6节。随后这段程序使用capric_w32函数执行第12.1.3节中要求的寄存器操作,之后可以使用轮询方式,或者使用中断方式唤醒这个DMA写进程。当进程被唤醒后,表示DMA写操作已经完成,此时这段程序使用copy_to_user函数将数据复制到用户空间。

值得注意的是,这段代码使用了interruptible_sleep_on函数将当前进程休眠,而在中断处理程序中使用wake_up_interruptible函数将其唤醒。这是一种非常糟糕的实现方式,而且存在相当大的隐患。

interruptible_sleep_on函数的主要工作是将当前进程放入等待队列中睡眠,目前在Linux系统中,该函数已经逐步被wait_event_interruptible函数取代,但这并不是问题的关键。在源代码12-7中,即便使用wait_event_interruptible函数也存在同样的问题。(www.chuimin.cn)

因为interruptible_sleep_on函数的执行路径较长,很可能在当前进程还没有被该函数放入adapter->dma_write_wait队列时,处理器已经执行中断服务例程,打断interruptible_sleep_on函数,并执行wake_up_interruptible函数。Capric中断服务例程的详细说明见第12.3.4节。

wake_up_interruptible函数将唤醒在adapter->dma_write_wait队列中休眠的进程,而此时当前进程可能还没有被加入到等待队列中。当该函数执行完毕退出中断处理例程之后,处理器继续执行capric_read函数,并完成interruptible_sleep_on函数的执行,将自身加入到等待队列中睡眠。此时由于中断服务例程已经被提前执行,因此当前进程不会被wake_up_in-terruptible函数唤醒,从而造成死锁。

程序员可以使用DCSR1寄存器的msk和pending位解决这个死锁问题。采用这种方法时,设备驱动程序需要保证当前进程进入等待队列后,再允许Capric卡提交中断请求。但是这种方法将产生较长的中断延时,从而极大影响Capric卡的DMA读写效率。

程序员还可以使用interruptible_sleep_on_timeout或者wait_event_interruptible_timeout函数进行超时处理,使用该方法也可以解决上述死锁问题。

以上这两种方法都不是完美的解决方案,因为产生这种死锁的主要原因是Capric卡的逻辑设计并不合理。Capric卡使用的数据传送模型较为简单,系统程序员很难基于此模型写出高效的驱动程序。

2.DMA读的操作流程

Capric卡DMA读使用的函数与DMA写的类似,其流程如源代码12-8所示。

源代码12-8 Capric卡的DMA读

读者如果正确理解了上文关于DMA写的执行过程,DMA读的执行过程并不难理解。Capric卡DMA读的硬件操作流程如第12.1.4节所示。上述源代码使用capric_w32函数完成硬件寄存器的填写,然后可以使用轮询或者中断方式确定DMA读是否已经完成。在DMA读完成之后该例程将释放使用的内存资源后返回。与DMA写的操作流程类似,这段程序依然存在隐患。