Linux网络设备驱动程序深度理解
文章目录
- 1)概述
- 2)网络设备驱动程序位于哪个层级?
- 1、Linux内核体系结构
- 2、Linux内核体系结构(源码视图)
- 3)网络设备对比块设备/字符设备
- 4)硬件模型
- 1、硬件实现哪个层级?
- 2、常见的硬件模型(以太网)
- 5)数据结构及接口
- 1、sk_buff (socket buffer)
- 2、驱动中对网络数据的操作接口
- 6)DM9000驱动程序(以太网)
- 1)功能逻辑框图
- 2)硬件模型
- 3)驱动代码分析
- 4)MII/MDIO
- 5)驱动目录phy和ethenet
往期文章,Linux内核网络子系统框架介绍:https://blog.csdn.net/STCNXPARM/article/details/151253176
1)概述
1、网络设备驱动模型(Network Device Driver Model):适用于网络设备驱动的模型。驱动程序通过网络设备驱动接口(Network Device Driver Interface)与网络设备进行通信。
2、有了字符设备驱动程序的基础,学习网络驱动程序应该进行差异化学习线路
3、网络上很多教程都只是简单介绍?由于linux网络协议栈内容非常庞大,要讲清楚极其困难,系统协议栈已经非常稳定和成熟,对于新增一个网络设备驱动程序,只需要实现对应接口即可。
4、多个应用同时申请网络数据,怎么区分?
使用port来分区应用程序,IP用来识别哪台机器;
5、一个应用申请连续的数据,怎么实现?
站在驱动的角度,我们更关注单次的数据往返逻辑,连续的数据无非就是单次的循环过程。
6、这里介绍通用的网络驱动程序框架(即无论是有线网络还是无线网络都适用),部分以 “以太网设备”为例子;
2)网络设备驱动程序位于哪个层级?
1、Linux内核体系结构
1)网络设备驱动程序位于设备驱动功能层;
2)linux驱动就是 实现网络设备接口层定义的接口,以及硬件的初始化;
2、Linux内核体系结构(源码视图)
1)dev.c 是由内核实现的稳定框架;
2)驱动开发者 实现填充dev.c对应的接口即可,如8390.c;
3)网络设备对比块设备/字符设备
1)对于网络设备,没有open,没有/dev/设备文件,取而代之是socket;
2)网络设备的read/write同样会经过VFS,但之后转入到内核的网络协议栈,没有直接对接到设备驱动程序;
4)硬件模型
1、硬件实现哪个层级?
1)一般协议中的各层软件实现和硬件实现(不一定完全对应,尤其是数据链路层,软件实现或硬件实现 具体情况具体分析);
2)对于PHY,一定是硬件实现,软件工程师可简单了解即可;
3)MAC 是一组用于决定如何访问介质与传送数据的规则(数据帧格式-MAC帧),即发出的数据如何给对方正确接收;
2、常见的硬件模型(以太网)
1)不同产品采用的集成方案不同,对应的驱动实现也有所不同,但对接上一层级的接口不会变化;
2)芯片举例:DM9000(集成MAC层和PHY层)、DM9161(PHY层);
3)现代产品大多数CPU集成MAC层;
5)数据结构及接口
1、sk_buff (socket buffer)
/android/vendor/amlogic/common/kernel/common_5.4/include/linux/skbuff.h
struct sk_buff {struct sk_buff *next;struct sk_buff *prev;struct net_device *dev;struct sock *sk;unsigned int len, data_len;__u16 mac_len, hdr_len;__u32 priority;__u16 protocol;__be16 inner_protocol;__u16 inner_transport_header;__u16 inner_network_header;__u16 inner_mac_header;sk_buff_data_t tail;sk_buff_data_t end;unsigned char *head, *data;
}
1、sk_buff结构体是套接字缓冲区,是内核网络数据传输的基本单位,用于Linux网络子系统中的各层之间的传递数据(head、data、tail、end这些指针在传递过程不断变化),是linux网络子系统数据传递的“中枢神经”;
2、怎么理解 套接字?在向下传递过程中,各层会将不同各自的协议头加入到skbuff中去,组成最终的网络包发送出去;同理,向上传递,则会剥离各自的协议头直至交给用户;很形象的”套接“动作!
2、驱动中对网络数据的操作接口
1)网络协议接口层数据收发接口
/android/vendor/amlogic/common/kernel/common_5.4/net/core/dev.c
int dev_queue_xmit(struct sk_buff *skb)
int netif_rx(struct sk_buff *skb)2)网络设备的打开和释放,收发数据需要先激活
/android/vendor/amlogic/common/kernel/common_5.4/include/linux/netdevice.h
static inline void netif_start_queue(struct net_device *dev) //在驱动open中使用,激活设备发送队列
static inline void netif_stop_queue(struct net_device *dev) //在驱动close中使用,关闭设备发送队列3)网络设备的数据发送的开启和关闭
net_device_ops->ndo_start_xmit() //上层调用驱动提供的传输函数(网络层的dev_queue_xmit方法调用)
static inline void netif_wake_queue(struct net_device *dev) //通知上层可以发送数据(当设备空闲时驱动调用,一般在发送完msg后调用)
static inline void netif_stop_queue(struct net_device *dev) //通知上层禁止发送数据(当设备忙碌时驱动调用,一般在开始发送msg时调用,避免冲突)
6)DM9000驱动程序(以太网)
1)功能逻辑框图
2)硬件模型
1、网卡与CPU的连接分三部分:控制引脚(片选、中断) 、数据引脚和地址引脚;
2、DM9000AE引脚图
3)驱动代码分析
1、网卡DM9000裸机驱动详解
https://zhuanlan.zhihu.com/p/3749418342、uboot对网络的支持
/android/bootloader/uboot-repo/bl33/v2015/net/eth.c
/android/bootloader/uboot-repo/bl33/v2019/drivers/net/dm9000x.c
int dm9000_initialize(bd_t *bit) //在对应的board里面调用3、内核中对ethernet的支持
/android/vendor/amlogic/common/kernel/common_5.4/include/linux/etherdevice.h
/android/vendor/amlogic/common/kernel/common_5.4/net/ethernet/eth.c4、dm9000驱动程序采用platform平台设备驱动模型
/android/vendor/amlogic/common/kernel/common_5.4/drivers/net/ethernet/davicom/dm9000.c
static struct platform_driver dm9000_driver = {.driver = {.name = "dm9000",.pm = &dm9000_drv_pm_ops,.of_match_table = of_match_ptr(dm9000_of_matches),},.probe = dm9000_probe,.remove = dm9000_drv_remove,
};1)probe
int dm9000_probe()
{1.定义“对象”struct board_info *db; /* Point a board information structure */struct net_device *ndev;struct device *dev = &pdev->dev;2.分配并初始化各类硬件资源3.Allocates and sets up an Ethernet devicendev = alloc_etherdev(sizeof(struct board_info));4.将实现的接口挂接到net_device中去ndev->netdev_ops = &dm9000_netdev_ops;ndev->watchdog_timeo = msecs_to_jiffies(watchdog);ndev->ethtool_ops = &dm9000_ethtool_ops;db->mii.mdio_read = dm9000_phy_read; //mii接口db->mii.mdio_write = dm9000_phy_write;5.最后挂接到系统体系中去(链表结构),等待系统调用platform_set_drvdata(pdev, ndev);register_netdev(ndev);
}2)eth功能接口
static const struct net_device_ops dm9000_netdev_ops = {.ndo_open = dm9000_open, //当ifconfig eth0 up会调用到.ndo_stop = dm9000_stop, //当ifconfig eth0 down会调用到.ndo_start_xmit = dm9000_start_xmit, //当app发送数据时,会被调用到.ndo_tx_timeout = dm9000_timeout,.ndo_set_rx_mode = dm9000_hash_table,.ndo_do_ioctl = dm9000_ioctl,.ndo_set_features = dm9000_set_features,.ndo_validate_addr = eth_validate_addr,.ndo_set_mac_address = eth_mac_addr,
#ifdef CONFIG_NET_POLL_CONTROLLER.ndo_poll_controller = dm9000_poll_controller,
#endif
};3)eth管理接口
static const struct ethtool_ops dm9000_ethtool_ops = {.get_drvinfo = dm9000_get_drvinfo,.get_msglevel = dm9000_get_msglevel,.set_msglevel = dm9000_set_msglevel,.nway_reset = dm9000_nway_reset,.get_link = dm9000_get_link,.get_wol = dm9000_get_wol,.set_wol = dm9000_set_wol,.get_eeprom_len = dm9000_get_eeprom_len,.get_eeprom = dm9000_get_eeprom,.set_eeprom = dm9000_set_eeprom,.get_link_ksettings = dm9000_get_link_ksettings,.set_link_ksettings = dm9000_set_link_ksettings,
};4)待机、开机时由内核调用
static const struct dev_pm_ops dm9000_drv_pm_ops = {.suspend = dm9000_drv_suspend,.resume = dm9000_drv_resume,
};5)alloc_etherdev 分配接口
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)/android/vendor/amlogic/common/kernel/common_5.4/net/ethernet/eth.c
struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs,unsigned int rxqs)
{return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN,ether_setup, txqs, rxqs);
}
EXPORT_SYMBOL(alloc_etherdev_mqs);6)获取私有数据
struct board_info *db;
db = netdev_priv(ndev); //把挂接在ndev上的board_info结构体取出来7)register_netdev调用栈
/android/vendor/amlogic/common/kernel/common_5.4/net/core/dev.c
register_netdev()
--register_netdevice()
----call_netdevice_notifiers(NETDEV_POST_INIT, dev);
----netdev_register_kobject(dev); //在/sys/class/net目录下创建sysfs条目
----list_netdevice(dev); //将设备插入到其网络命名空间的全局设备列表中
----call_netdevice_notifiers(NETDEV_REGISTER, dev); //发送注册事件,内核的其它模块可以获悉并响应
----kobject_put(&dev->dev.kobj); //增加kobject引用计数
----rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U, GFP_KERNEL); //向用户空间发送netlink消息7)网卡设备接收网络数据
当网卡接收到数据包,会触发中断,进入驱动中断处理函数接收
/android/vendor/amlogic/common/kernel/common_5.4/drivers/net/ethernet/davicom/dm9000.c
--request_irq(dev->irq, dm9000_interrupt, irq_flags, dev->name, dev) //dm9000_open()里注册
----dm9000_interrupt(int irq, void *dev_id)
------dm9000_rx(dev)
--------netif_rx(skb);8)数据线和地址线的gpio配置
/android/vendor/amlogic/common/kernel/common_5.4/arch/arm/include/asm/io.h
db->io_addr = ioremap(db->addr_res->start, iosize);
db->io_data = ioremap(db->data_res->start, iosize);
writeb(reg_save, db->io_addr);
readb(db->io_addr);
4)MII/MDIO
1、MII(Media Independent Interface): 一个标准接口,将 MAC(在处理器或交换机中)连接到物理层收发器(PHY)。它定义了数据帧的传输和接收。
2、MDIO(Management Data Input/Output): 一个简单的两线串行总线(时钟线 MDC 和数据线 MDIO),是 MII 的一部分,专门用于 MAC 和 PHY 之间的管理通信。MAC 通过它来读写 PHY 芯片的内部寄存器,从而配置 PHY、监控链路状态、诊断故障。3、linux/mii.h 这个头文件提供了 以太网 PHY (物理层) 芯片 和 MAC (媒体访问控制) 控制器 之间进行管理和控制的标准化编程接口, 并定义了 IEEE 802.3 标准中规定的通用 PHY 寄存器地址。这些是任何 PHY 芯片都必须支持的基本寄存器。
5)驱动目录phy和ethenet
1、目录1:/drivers/net/phy/:负责 物理层 (PHY) 驱动。它直接管理硬件层面上的以太网PHY芯片,负责最基础的链路建立、信号转换和介质相关功能。
代表(DM9161)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/net/phy/davicom.c2、目录2:/drivers/net/ethernet/:负责 以太网控制器 (MAC) 驱动。它管理数据链路层中的MAC控制器,处理数据帧的组装、发送、接收以及更上层的协议交互。
代表(DM9000)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/net/ethernet/davicom/dm9000.c3、以太网通信芯片(DM9000AE、DM9161、LAN8720 的区别与联系)
https://blog.csdn.net/ftdlk/article/details/146011053
DM9000和DM9161的比较
https://blog.csdn.net/jackyard/article/details/8654809