加载设备驱动模块
设备驱动模块被编译成.o或者.ko,类似elf程序有一个入口main函数,.ko模块入口函数是module_init(igbuio_pci_init_module),igbuio_pci_init_module( )在insmod后首先执行。代码片段如下:
static int __init
igbuio_pci_init_module(void)
{
return pci_register_driver(&igbuio_pci_driver);
}
pci_register_driver是一个linux内核提供用来进行pci注册的标准接口,将igbuio_pci_driver 作为作为参数传入,这个结构在驱动程序里定义,作为驱动程序和PCI设联系的纽带。
static struct pci_driver igbuio_pci_driver = {
.name = "igb_uio",
.id_table = NULL, // uio设备驱动加载后不主动接管任何设备
.probe = igbuio_pci_probe,
.remove = igbuio_pci_remove,
};
.name:驱动名
.id_table:网卡id,驱动注册后接管的网卡id
.probe:注册处理函数
.remove:注销处理函数
常规网卡驱动,在open dev时候在申请数据存储的缓存(即数据包缓存队列),挂在adapter结构上,adapter结构是各个驱动的私有数据,为了统一管理约定位置紧挨着net_dev结构尾部,例如e1000网卡驱动的adapter:
struct e1000_adapter *adapter = netdev_priv(netdev);
/**
* netdev_priv - access network device private data
* @dev: network device
*
* Get network device private data
*/
static inline void *netdev_priv(const struct net_device *dev)
{
return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
}
adapter(即privata是不同网卡驱动的差异体现的地方)
缓存队列申请之后,需要和网卡dma对接,网卡才知道收到网络数据后DMA将数据考到哪里。申请对接代码:
static inline void *
dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle,
gfp_t gfp)
{
struct dma_map_ops *ops = get_dma_ops(dev);
void *memory;
gfp &= ~(__GFP_DMA | __GFP_HIGHMEM | __GFP_DMA32);
/* dma_handle:TSAD寄存器地址 */
if (dma_alloc_from_coherent(dev, size, dma_handle, &memory))
return memory;
if (!dev)
dev = &x86_dma_fallback_dev;
if (!is_device_dma_capable(dev))
return NULL;
if (!ops->alloc_coherent)
return NULL;
/* 申请内存获取虚拟地址以及物理地址,填充网卡数据发送地址寄存器TSAD */
memory = ops->alloc_coherent(dev, size, dma_handle,
dma_alloc_coherent_gfp_flags(dev, gfp));
debug_dma_alloc_coherent(dev, size, *dma_handle, memory);
return memory;
}
dma访问的是物理地址,它是怎么知道这块内存的物理地址呢?
1. 网卡数据发送地址寄存器 TSAD ,含义是”这个寄存器里的地址,是网卡缓存数据拷贝的对端地址”。BOIS在给PCI设备的寄存器分配了网卡寄存器的基地址,写进设备的配置空间。操作系统初始化时,为每个PCI分配了pci_dev结构,并把BIOS获取的并写到了配置空间的地址读出来写到resource字段中;
2. 网卡驱动在open设备的时候申请缓冲区内存ring,得到这块内存的虚拟地址(用于内核访问)和物理地址(用于DMA访问),然后将这个物理地址写到TSAD中。
此时,这个网卡驱动就接管了这个网络设备(网卡)。
那么,dpdk何时接管网卡呢?
在dpdk的uio模块中,uio获取网卡资源(resource)并保存到uio_info中,为用户态驱动准备资源。
/* Remap pci resources described by bar #pci_bar in uio resource n. */
static int
igbuio_pci_setup_iomem(struct pci_dev *dev, struct uio_info *info,
int n, int pci_bar, const char *name)
{
unsigned long addr, len;
void *internal_addr;
if (n >= ARRAY_SIZE(info->mem))
return -EINVAL;
addr = pci_resource_start(dev, pci_bar);
len = pci_resource_len(dev, pci_bar);
if (addr == 0 || len == 0)
return -1;
internal_addr = ioremap(addr, len);
if (internal_addr == NULL)
return -1;
info->mem[n].name = name;
info->mem[n].addr = addr; /* 网卡资源起始地址 物理地址 */
info->mem[n].internal_addr = internal_addr; /* 网卡资源起始地址,虚拟地址 */
info->mem[n].size = len;
info->mem[n].memtype = UIO_MEM_PHYS;
return 0;
}
以上代码为用户态驱动准备好了网卡资源,用户态通过internal_addr即可操作网卡寄存器。