说起DMA我们并不陌生,但是实际编程中去用的人不多吧,最多就是网卡驱动里的环形buffer,再有就是设备的dma,下面我们就分析分析.
DMA用来在设备内存和内存之间直接数据交互。而无需cpu干预
内核为了方便驱动的开发,已经提供了几个dma 函数接口。
dma跟硬件架构相关,所以linux关于硬件部分已经给屏蔽了,有兴趣的可以深入跟踪学习.
按照linux内核对dma层的架构设计,各平台dma缓冲区映射之间的差异由内核定义的一个dma操作集
include/linux/dma-mapping.h:点击(此处)折叠或打开struct dma_map_ops {
void* (*alloc)(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp,
struct dma_attrs *attrs);
void (*free)(struct device *dev, size_t size,
void *vaddr, dma_addr_t dma_handle,
struct dma_attrs *attrs);
int (*mmap)(struct device *, struct vm_area_struct *,
void *, dma_addr_t, size_t, struct dma_attrs *attrs);
int (*get_sgtable)(struct device *dev, struct sg_table *sgt, void *,
dma_addr_t, size_t, struct dma_attrs *attrs);
dma_addr_t (*map_page)(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction dir,
struct dma_attrs *attrs);
void (*unmap_page)(struct device *dev, dma_addr_t dma_handle,
size_t size, enum dma_data_direction dir,
struct dma_attrs *attrs);
int (*map_sg)(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction dir,
struct dma_attrs *attrs);
void (*unmap_sg)(struct device *dev,
struct scatterlist *sg, int nents,
enum dma_data_direction dir,
struct dma_attrs *attrs);
void (*sync_single_for_cpu)(struct device *dev,
dma_addr_t dma_handle, size_t size,
enum dma_data_direction dir);
void (*sync_single_for_device)(struct device *dev,
dma_addr_t dma_handle, size_t size,
enum dma_data_direction dir);
void (*sync_sg_for_cpu)(struct device *dev,
struct scatterlist *sg, int nents,
enum dma_data_direction dir);
void (*sync_sg_for_device)(struct device *dev,
struct scatterlist *sg, int nents,
enum dma_data_direction dir);
int (*mapping_error)(struct device *dev, dma_addr_t dma_addr);
int (*dma_supported)(struct device *dev, u64 mask);
int (*set_dma_mask)(struct device *dev, u64 mask);
#ifdef ARCH_HAS_DMA_GET_REQUIRED_MASK
u64 (*get_required_mask)(struct device *dev);
#endif
int is_phys;
}
来统一屏蔽实现的差异.
不同差异主要来来自cache的问题
Cache与dma同步问题,这里不深入讨论.
另外一个常用的函数是Dma_set_mask, 为了通知内核设备能够寻址的范围,很多时候设备能够寻址的范围有限。
Dma映射可以分为三类:
1.一致性dma映射dma_alloc_coherent(问题:驱动使用的buffer不是自身申请的,而是其他模块)
当驱动模块主动分配一个Dma缓冲区并且dma生存期和模块一样时
参数说明:
(1)这个函数的返回值是缓冲的一个内核虚拟地址,它可被驱动使用
(2)第三个参数dma_handle:
其间相关的物理地址在dma_handle中返回
2.流式dma映射dma_map_single
通常用于把内核一段buffer映射,返回物理地址.
如果驱动模块需要使用从别的模块传进来的虚拟地址空间作为dma缓冲区,保证地址的线性cache一致性
一致性api接口:sync_single_for_cpu
3.分散/聚集映射(scatter/gather map) Dma_map_sgs
有时候我们还需要
1. 回弹缓冲区bounce buffer:当cpu侧物理地址不适合设备的dma操作的时候
2.
DmA内存池:一般dma映射都是单个page的整数倍,如果驱动程序需要更小的一致性映射的dma缓冲区,可以使用。类似于slab机制,
Dma_pool_create
下面我们就那网卡驱动的例子说说dma的具体应用,参考linux kernel e1000网卡
drivers/net/ethernet/intel/e1000/*
Ring buffer
Dma不能为高端内存,一般为32,默认低端内存,由于设备能够访问的地址范围有限。
设备使用物理地址,而代码使用虚拟地址。
就看看如何发送数据包:e1000_main.c:
e1000_xmit_frame: 关于帧的发送流程这里不多说.
点击(此处)折叠或打开
static netdev_tx_t e1000_xmit_frame(struct sk_buff *skb,
struct net_device *netdev)
{
struct e1000_adapter *adapter = netdev_priv(netdev);
struct e1000_hw *hw = &adapter->hw;
struct e1000_tx_ring *tx_ring;
unsigned int first, max_per_txd = E1000_MAX_DATA_PER_TXD;
unsigned int max_txd_pwr = E1000_MAX_TXD_PWR;
unsigned int tx_flags = 0;
unsigned int len = skb_headlen(skb);
unsigned int nr_frags;
unsigned int mss;
int count = 0;
int tso;
unsigned int f;
/* This goes back to the question of how to logically map a tx queue
* to a flow. Right now, performance is impacted slightly negatively
* if using multiple tx queues. If the stack breaks away from a
* single qdisc implementation, we can look at this again. */
tx_ring = adapter->tx_ring;
if (unlikely(skb->len <=