openharmony以太网网卡驱动剖析
Linux网络设备驱动架构
驱动架构自上而下分为4层:
-
协议接口层
-
设备接口层
-
设备驱动功能层
-
网络设备与媒介层
协议接口层
协议接口层主要功能是给上层协议提供接收和发送的接口。当内核协议栈需要发送数据时,会通过调用 dev_queue_xmit 函数来发送数据。
dev_queue_xmit执行流程
同样内核协议栈接收数据也是通过协议接口层的 netif_rx 函数来进行的。传递的数据被描述为套接字缓冲区,用struct sk_buff结构描述,在openharmony中,该结构体定义位于kernel/linux/linux-5.10/include/linux/skbuff.h中,用于在网络子系统中的各层之间传输数据,该结构在整个网络收发过程中贯穿始终。
sk_buff 是 Linux 网络驱动中一个非常 重要的结构体,网络数据就是以 sk_buff 保存的,各个协议层在 sk_buff 中添加自己的协议头,最终由底层驱动将 sk_buff 中的数据发送出去。网络数据的接收过程恰好相反,网络底层驱动将 接收到的原始数据打包成 sk_buff,然后发送给上层协议,上层会取掉相应的头部,然后将最终的数据发送给用户。
设备接口层
网络设备接口层用于抽象各种不同的网络设备,Linux 内核使用 net_device 结构体表示一个具体的网络设备,net_device 是整个网络驱动的灵魂。网络驱动的核心就是初始化 net_device 结构体中的各个成员变量,然后将初始化完成以 后的 net_device 注册到 Linux 内核中。在openharmony中,该结构体定义位于kernel/linux/linux-5.10/include/linux/netdevice.h中。net_device 是一个庞大的结构体。
下面介绍一些关键的成员变量,如下:
1、name 是网络设备的名字。
2、mem_end 是共享内存结束地址。
3、mem_start 是共享内存起始地址。
4、base_addr 是网络设备 I/O 地址。
5、irq 是网络设备的中断号。
6、dev_list 是全局网络设备列表。
7、napi_list 是 napi 网络设备的列表入口。
8、unreg_list 是注销(unregister)的网络设备列表入口。
9、close_list 是关闭的网络设备列表入口。
10、netdev_ops 是网络设备的操作集函数,包含了一系列的网络设备操作回调函数,稍后会讲解 netdev_ops 结构体。
设备驱动功能层
net_device 有个非常重要的成员变量:struct net_device_ops。,这就是网络设备的操作集,来描述对网卡的各种操作。以海思芯片为例,net_device_ops操作集定义在kernel/linux/linux-5.10/drivers/net/ethernet/hisilicon/hisi_femac.c中。net_device_ops 结构体里面都是一些以“ndo_”开头的函数,这些函数就需要网络驱动编写人员去实现,不需要全部都实现,根据实际驱动情况实现其中一部分即可。
1、ndo_open 函数,打开网络设备的时候此函数会执行,网络驱动程序需要实现此函数,非常重要!会在此函数中做如下工作:
初始化接收缓冲区。
- 如果使用 NAPI 的话要使能 NAPI 模块,通过 napi_enable 函数来使能。
- 硬件相关初始化。
- 开启 PHY。
- 调用 netif_tx_start_all_queues 来使能传输队列,也可能调用 netif_start_queue 函数。
2、ndo_stop 函数,关闭网络设备的时候此函数会执行,网络驱动程序也需要实现
此函数。会在此函数中做如下工作:
- 停止 PHY。
- 停止 NAPI 功能。
- 停止发送功能。
- 关闭 MAC。
- 断开 PHY 连接。
- 释放数据缓冲区。
3、ndo_start_xmit 函数,当需要发送数据的时候此函数就会执行,此函数有一个参数为 sk_buff 结构体指针,sk_buff 结构体在 Linux 的网络驱动中非常重要,sk_buff 保存了上层传递给网络驱动层的数据。也就是说,要发送出去的数据都存在了 sk_buff 中。如果发送成功的话此函数返回 NETDEV_TX_OK,如果发送失败了就返回 NETDEV_TX_BUSY,如果发送失败了我们就需要停止队列。
4、ndo_set_rx_mode 函数,此函数用于改变地址过滤列表,根据 net_device 的 flags
成员变量来设置 SoC 的网络外设寄存器。比如 flags 可能为 IFF_PROMISC、IFF_ALLMULTI 或
IFF_MULTICAST,分别表示混杂模式、单播模式或多播模式。
5、ndo_set_mac_address 函数,此函数用于修改网卡的 MAC 地址,设置 net_device
的 dev_addr 成员变量,并且将 MAC 地址写入到网络外设的硬件寄存器中。
6、ndo_do_ioctl 函数,用户程序调用 ioctl 的时候此函数就会执行,比如 PHY 芯
片相关的命令操作,一般会直接调用 phy_mii_ioctl 函数。
初始化
首先从platform 开始。
static struct platform_driver hisi_femac_driver = {
.driver = {
.name = "hisi-femac",
.of_match_table = hisi_femac_match,
},
.probe = hisi_femac_drv_probe,
.remove = hisi_femac_drv_remove,
#ifdef CONFIG_PM
.suspend = hisi_femac_drv_suspend,
.resume = hisi_femac_drv_resume,
#endif
};
module_platform_driver(hisi_femac_driver);
在这个函数中,定义了一个静态的平台驱动结构体 hisi_femac_driver,用于与海思的以太网访问控制器设备进行交互。通过 probe 和 remove 函数完成设备的初始化和移除操作,最后,使用 module_platform_driver 宏来注册这个平台驱动。
驱动和设备匹配上后,会调用驱动的 probe 函数hisi_femac_drv_probe。
函数较长,仅截取部分。该函数为 struct net_device 结构体申请内存,并进行了初始化。
最终会调用ether_setup函数对dev成员进行初始化。初始化完ndev后,设置了netdev_ops 和 mac 地址,最后调用register_netdev函数注册了网络设备。
接着分析net_device_ops,前面已经介绍过net_device_ops。当用户执行命令ifconfig eth0 up后会调用网卡驱动的 open 函数,即hisi_femac_net_open函数。
发送过程分析
应用程序调用send函数去发送数据,内核协议栈会将数据构造成struct sk_buff后放入等待队列,调用start_xmit通知网卡发送数据。即调用hisi_femac_net_xmit函数。
函数较长,仅截取部分。
更多推荐
所有评论(0)