Hi3520d 网卡驱动源码分析
一、ioremap_nocache
跟体系结构有关
二、kzalloc 与kmalloc
kzalloc调用kmalloc,在此基础上申请内存出初始化0
GFP_KERNEL:可以休眠,函数可重入
GFP_ATOMIC:不可以休眠,一般放在终端上下文申请
三、网卡驱动Src: src/drivers/net/hieth-sf/net.c
1、驱动模块
module_init(hieth_init);
module_exit(hieth_exit);
2、驱动模块init
ret =platform_device_register(&hieth_platform_device);
ret =platform_driver_register(&hieth_platform_driver);
3、平台驱动数据
static struct platform_driver hieth_platform_driver= {
.probe = hieth_plat_driver_probe,(见第5小节)
.remove =hieth_plat_driver_remove,
.suspend =hieth_plat_driver_suspend,
.resume =hieth_plat_driver_resume,
.driver = {
.owner = THIS_MODULE,
.name = HIETH_DRIVER_NAME,
.bus = &platform_bus_type,
},
};
4、平台设备数据
static struct platform_devicehieth_platform_device = {
.name = HIETH_DRIVER_NAME,
.id = 0,
.dev = {
.platform_data = NULL,
.dma_mask = (u64 *)~0,
.coherent_dma_mask = (u64)~0,
.release =hieth_platform_device_release,
},
.num_resources = ARRAY_SIZE(hieth_resources),
.resource = hieth_resources,
};
5、驱动探针hieth_plat_driver_probe
A. 网卡系统初始化: hieth_sys_init();
B.mdio bus初始化: hieth_mdiobus_driver_init();
C. 网卡私有probe: hieth_platdev_probe_port();(见第四节)
D. 注册网卡中断: request_irq(hieth_net_isr);(见第九节)
四、网卡私有probe:hieth_platdev_probe_port
1、申请网络设备
struct net_device *netdev = NULL;
struct hieth_netdev_local *ld;
netdev = alloc_etherdev(sizeof(*ld))
其中:alloc_etherdev defined alloc_etherdev_mqs调用alloc_netdev_mqs
针对alloc_netdev_mqs函数:
(1)使用kzalloc申请网卡设备,总大小:sizeof(struct net_device) + sizeof_priv(= sizeof(hieth_netdev_loca))。也就是说网卡设备数据结构后面紧跟着私有网卡数据结构,后续使用通过net_priv(dev)获取私有数据
(2)硬件地址MAC,组播地址等初始化
dev_addr_init(),dev_mc_init(),dev_uc_init(),
(3)ether_setup对网卡设备部分成员初始化
(4)申请收发队列数
netif_alloc_netdev_queues和 netif_alloc_rx_queues为每个队列申请队列头,此处收发各只有1个队列,发送队列的长度为1000
(5)拷贝网卡名dev->name = eth%d
(6)私有数据定义
structhieth_netdev_local:
struct sk_buff_head rx_head; /*received pkgs*/
struct sk_buff_head rx_hw; /*rx pkgs in hw*/
struct sk_buff_head tx_hw; /*tx pkgs in hw*/
int tx_hw_cnt;
2、网卡设备成员进行设置
netdev->watchdog_timeo = 3*HZ; 网卡传输超时(发送)以jffies为单位
超时后最终会调用hieth_net_timeout这个进行超时处理
netdev->netdev_ops = &hieth_netdev_ops; 网卡设备操作函数初始化(见第五节)
netdev->ethtool_ops = &hieth_ethtools_ops; 控制网络接口参数所需函数初始化(见第八节)
3、映射网络地址控制器IO空间
ld->iobase = (unsignedlong)ioremap_nocache(CONFIG_HIETH_IOBASE, CONFIG_HIETH_IOSIZE);
4、网络端口复位和初始化
/* reset and init port */
hieth_port_reset(ld, ld->port);
hieth_port_init(ld, ld->port);
5、硬件发送队列深度
ld->depth.hw_xmitq =CONFIG_HIETH_HWQ_XMIT_DEPTH; (=12)
接收则为:64 - 12 = 52
6、同PHY建立连接phy_connect()
phydev->adjust_link= hieth_adjust_link;
其中接口:获取link , duplex,speed等信息,另外打印是up还是down
7、skb队列头初始化
skb_queue_head_init(&ld->rx_head);
skb_queue_head_init(&ld->rx_hw);
skb_queue_head_init(&ld->tx_hw);
8、申请接收队列缓冲区hieth_init_skb_buffers
调用dev_alloc_skb()原子分配,大小为:1024* 2KB,即skb接收池共1024个skb,每个skb是2KB
Skb申请后对部分参数初始化:atomic_set(&skb->users,1);
9、注册网卡设备
register_netdev(netdev);
五、网卡操作函数
static const struct net_device_opshieth_netdev_ops = {
.ndo_open =hieth_net_open, (见第六节)
.ndo_stop = hieth_net_close,
.ndo_start_xmit =hieth_net_hard_start_xmit,(见第七节)
.ndo_tx_timeout =hieth_net_timeout,
.ndo_do_ioctl =hieth_net_ioctl,
.ndo_set_mac_address =hieth_net_set_mac_address,
.ndo_get_stats = hieth_net_get_stats,
};
六、hieth_net_open打开网卡
1、基本流程
(1)初始化网卡数据接收函数,使用tasklet机制
ld->bf_recv.func = hieth_bfproc_recv(见第3小节); bf_recv在中断中被调用
bf_recv为tasklet,
(2)hieth_set_hwq_depth(ld);
硬件接收和发送帧队列深度设置,发送12个,接收52个。
注:软件至少需要申请与硬件接收深度相同个数的缓冲区,和硬件通过DMA一一映射。
(3)netif_carrier_off(dev);设备载波丢失
(4)hieth_feed_hw(ld);(见第2小节),填充硬件接收队列,硬件接收到的数据直接放入skb(DMA映射)
(5)启动队列netif_start_queue(dev);
(6)启动PHY,and 开启中断
(7) 监控定时器ld->monitor.function = hieth_monitor_func;(100ms)(见第4小节)
(8)支持EEE的相关设置
2、hieth_feed_hw,缓冲区skb首地址依次写入硬件接收队列
将skb通过DMA直接映射给硬件使用(目的减少数据拷贝)
(1).检测是否可配置输入队列帧首地址
(2).从接收队列缓冲池中查找空闲的skb
hieth_platdev_alloc_skb()中atomic_inc(&skb->users)
这时skb->users=2,这样网络层就不会释放skb。
(3).将该空闲skb通过DMA映射提供给网卡接收数据DMA_FROM_DEVICE
(4).将缓冲区skb首地址写入输入帧首地址寄存器IQ_ADDR
(5).将skb放入接收硬件接收队列中,便于管理
注意:这是一个while循环直到不可配置为止,将硬件可配置队列(52个)填满为止。
(6)dma_map_single注释:流式DMA映射
映射一块处理器的虚拟地址,这样可以让外设访问。该函数返回内存的物理地址。
DMA_NONE 仅用于调试目的
DMA_TO_DEVICE 数据从内存传输到设备,可认为是写操作。
DMA_FROM_DEVICE 数据从设备传输到内存,可认为是读操作。
DMA_BIDIRECTIONAL 不清楚传输方向则可用该类型。
疑问1:写寄存器不断覆盖,是否只有最后一次有效。
解答1:每写一次就加入硬件管理队列,直至写满为止(根据是否可以配置输入队列地址来判断)
疑问2:dma映射后没有找到unmap
3、数据接收函数hieth_bfproc_recv(中断调用bf_recv)
(1)hieth_hw_recv_tryup将硬件队列数据映射到内存
A.查询中断状态是否有帧等待CPU接收
B.获取帧长度
C.从硬件队列获取数据后,添加到接收队列头
skb = skb_dequeue(&ld->rx_hw);
skb_queue_tail(&ld->rx_head, skb);
(2)从接收队列头获取skb
(3)对接收到数据进行简单判断
(4)传递给上层协议处理netif_rx(skb);
注意:循环操作
疑问:skb源自缓冲池,只在probe时申请,如果释放了又没有申请,为啥还可以使用。
答案:结合4.8小节和6.2小节控制skb->users来达到目的,只有当skb->users时free_skb才会释放skb。
4、hieth_monitor_func定时监控函数
(1)填充硬件接收队列
hieth_feed_hw(ld);
(2)释放队列已经发送的skb
hieth_xmit_release_skb(ld);
七、数据发送hieth_net_hard_start_xmit(由中断启动)
1、 释放队列已经发送的skb hieth_xmit_release_skb(ld);
(1)检查输出队列出队index是否小于硬件发送队里数
如果小于,说明已经有发送完成的,那么就需要进行skb释放
(2)从硬件队列删除已发送skb
skb_dequeue(&ld->tx_hw);
(3)释放skb
注意:循环操作
2、用户数据skb进行dma映射DMA_TO_DEVICE
3、发送用户数据hieth_xmit_real_send(ld,skb);
(1)检查是否可以配置输出队列
(2)将输出帧skb首地址写入寄存器
(3)将输出帧skb加入硬件发送队列。
4、清中断,启动硬件发送
在未发送完成前,告诉内核停止发送队列:netif_stop_queue(dev);
使能中断,发送完成后进入中断:hieth_irq_enable(ld,UD_BIT_NAME(HIETH_INT_TXQUE_RDY));
注:当硬件发送完成后会产生中断,中断里会告知内核重新启动发送,禁止中断见(9.2节)
八、网卡设备设备操作函数
staticstruct ethtool_ops hieth_ethtools_ops = {
.get_drvinfo = hieth_ethtools_get_drvinfo,
.get_link = hieth_ethtools_get_link,
.get_settings = hieth_ethtools_get_settings,
.set_settings = hieth_ethtools_set_settings,
};
九、注册网卡中断: request_irq(hieth_net_isr)
中断的主要目的就是接收和处理网卡数据,传递给协议层。
ret= request_irq(CONFIG_HIETH_IRQNUM, hieth_net_isr, IRQF_SHARED,"hieth", hieth_devs_save);
中断号:56,
IRQF_SHARED:与其他设备共享中断号主要是上行和下行端口
中断处理函数:hieth_net_isr
网卡设备:hieth_devs_save,定义如下:struct net_device*hieth_devs_save[2]指向上行和下行口
1、hieth_net_isr
(1)屏蔽所有中断
(2)读取中断状态
(3)中断处理hieth_net_isr_proc(见第2个小节)
(4)清楚中断状态
(5) 开启使能各个中断掩码
2、中断处理hieth_net_isr_proc
产生中断的情况:新报文到,或者外出报文发送完成
(1)处理接收到的数据
根据处理器,收到8个数据包后产生一个中断,如果没有8个数据包则根据超时产生中断。
将网卡数据映射到内存skb,见6.3节:
hieth_hw_recv_tryup(ld)
然后启动tasklet机制,启动后半部操作:
tasklet_schedule(&ld->bf_recv);具体操作函数见6.3
(2)硬件发送完成,中断禁止,告之内核可以重启启动发送队列
hieth_irq_disable(ld,UD_BIT_NAME(HIETH_INT_TXQUE_RDY));
netif_wake_queue(hieth_devs_save[ld->port]);
发送数据见第七节,最后会启动中断。
十、总结
该驱动通过中断来处理帧的接收和发送。接收时有底层申请好缓冲池,循环利用从不释放。没有使用NAPI机制。比较简单的网络数据处理。