如下图,一般网络硬件都由三部分组成,分别为内部MAC + 外部PHY + RJ45 ,共同组成了一个完整的嵌入式网络接口硬件。MAC跟PHY的通讯通过MDIO总线 和 MII/RMII/GMII/RGMII等接口完成,其中驱动程序可以通过 MDIO 总线访问 PHY 芯片的任意一个寄存器,来完成对PHY的配置,而网络数据传输则通过 MII/RMII/GMII/RGMII等接口完成。
下图为stmmac网络驱动框架,主要是注册流程和数据传输机制,可以根据这个拓扑图去阅读代码,这个框图很重要,后续我会分别进行讲解
- 源码目录: stmmac所有的源代码都在drivers/net/ethernet/stmicro/stmmac目录下
- Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。
- 通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。通知链表机制并不复杂,实现它的代码只有区区几百行。
- 事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
- 源码路径: driversnetethernetstmicrostmmacstmmac_main.c
首先,在驱动入口函数中,调用register_netdevice_notifier函数申请创建网络设备通知事件的操作,反之出口函数调用unregister_netdevice_notifier函数
- 入口函数
static struct notifier_block stmmac_notifier = {
.notifier_call = stmmac_device_event,
};
static int __init stmmac_init(void)
{
#ifdef CONFIG_DEBUG_FS
if (!stmmac_fs_dir)
stmmac_fs_dir = debugfs_create_dir(STMMAC_RESOURCE_NAME, NULL);
register_netdevice_notifier(&stmmac_notifier);
#endif
return 0;
}
module_init(stmmac_init)
- 出口函数
static void __exit stmmac_exit(void)
{
#ifdef CONFIG_DEBUG_FS
unregister_netdevice_notifier(&stmmac_notifier);
debugfs_remove_recursive(stmmac_fs_dir);
#endif
}
module_exit(stmmac_exit)
3.3 网络GAMC入口出口函数,以S32G274A为例
一般网络GAMC驱动都是用的platform驱动框架,方便与设备树之间进行匹配和方便移植,具体初始化如下
- 源码路径 : driversnetethernetstmicrostmmacdwmac-s32cc.c
static struct platform_driver s32_dwmac_driver = {
.probe = s32cc_dwmac_probe,
.remove = s32cc_dwmac_remove,
.driver = {
.name = "s32cc-dwmac",
.pm = &stmmac_pltfr_pm_ops,
.of_match_table = s32_dwmac_match,
},
};
module_platform_driver(s32_dwmac_driver);
- module_platform_driver 主要是做一个设备树的数据获取和驱动设备间的链接建立,这里主要看s32cc_dwmac_probe这个入口函数
- 获取设备树硬件资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - 根据设备树信息,初始化gmac状态
ret = s32cc_gmac_init(pdev, gmac); - 初始化工作队列,网络硬件设备,这里调用的是stmmac_dvr_probe函数,起作用就是初始化工作队列,处理各种事件;初始化gamc硬件,填充gamc的回调函数,比如设备打开关闭和接收发送等
这个的ops结构体,指的就是前面提到的stmmac_dvr_probe函数中调用的回调函数结构体,填充struct net_device_ops,该结构体主要包括下列成员,可以选择性查看,成员较多,用到再查;以stmmac为例,他调用了下列这几个成员,其他成员会解析在后面
static const struct net_device_ops stmmac_netdev_ops = {
.ndo_open = stmmac_open,
.ndo_start_xmit = stmmac_xmit,
.ndo_stop = stmmac_release,
.ndo_change_mtu = stmmac_change_mtu,
.ndo_fix_features = stmmac_fix_features,
.ndo_set_features = stmmac_set_features,
.ndo_set_rx_mode = stmmac_set_rx_mode,
.ndo_tx_timeout = stmmac_tx_timeout,
.ndo_do_ioctl = stmmac_ioctl,
.ndo_setup_tc = stmmac_setup_tc,
.ndo_select_queue = stmmac_select_queue,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = stmmac_poll_controller,
#endif
.ndo_set_mac_address = stmmac_set_mac_address,
.ndo_vlan_rx_add_vid = stmmac_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = stmmac_vlan_rx_kill_vid,
};
-
ndo_init
注册网络设备时调用,驱动程序一般不使用,虚拟网络设备可能使用 -
ndo_uninit
网络设备卸载时调用,驱动程序一般使用 -
ndo_open
禁止所有中断、关闭载波(netif_carrier_off);
分配并初始化传输和接收描述符环内存(使用dma_alloc_coherent分配确保EMAC和CPU都可以访问);
分配发送skb处理状态的链表或者数组空间(使用kzalloc),其实也可以用sk_buff_head相关的数据结构和函数;
初始化接收描述符环,包括分配struct sk_buff以及数据缓存区(netdev_alloc_skb),并且进行DMA映射(dma_map_single);
注册中断相关函数(request_irq);
初始化MAC硬件模块(设置MAC相关过滤参数、填写传输和接收描述符初始化地址dma_addr_t、中断设置、聚合功能配置);
PHY芯片初始化并启动;
使能NAPI模块(napi_enable);
调用netif_start_queue或者netif_tx_start_all_queues使能传输队列调度;
调用netif_wake_queue或者netif_tx_wake_all_queues开启传输队列的调度(因为此时所有的传输环为空,可以立刻开始传输);
使能传输/接收通道,打开中断,开启定时器功能; -
ndo_stop
停止上层传输调度;
停止NAPI功能;
停止MAC接收功能;
等待既有的skb数据发出去,释放skb跟踪链表;
停止发送功能;
禁止中断功能;
手动处理已接收到的数据包传递给上层协议;
释放接受和发送描述符环内存空间;
释放接收缓存数据空间;
注册中断函数;
除NAPI功能(netif_napi_del);
止并删除timer功能、取消并删除delaywork;
闭载波netif_carrier_off;
闭MAC和PHY电源; -
ndo_start_xmit
检查skb_shinfo(skb)->tx_flags标志位,SKBTX_HW_TSTAMP指示硬件向上层软件提供硬件时间戳(调用skb_tstamp_tx 、skb_tx_timestamp),设置过程中置位SKBTX_IN_PROGRESS,表示硬件正在处理上层的时间戳提取要求;
根据ip_summed查看是否需要协助计算校验和,需要csum_start和csum_offset;
是否需要VLAN标签插入,需要vlan_proto和vlan_tci;
queue_mapping指示该SKB需要插入到哪个传输队列上;
根据nr_frags检查是否需要发送碎片包,此时需要初始化nr_frags数量的传输描述符;
检查skb->encapsulation,确定是否需要使用内层协议头;
检查skb_shinfo(skb)->gso_size是否需要硬件进行分片操作;
映射DMA操作需要的地址,填入传输描述符中;
更新传输描述环相关状态;
记录该等待传输的SKB到链表中;
如果传输环空间不够应停止上层继续调度,也可以调用BQL机制(netdev_tx_sent_queue)实现自动调度控制;
检查skb是否为jumbo帧,硬件是否支持直接发送;
检查skb->no_fcs确定是否硬件需要在帧尾部加上FCS; -
ndo_select_queue
选择传输队列 -
ndo_change_rx_flags
通知驱动程序netdev->flags标记(IFF_PROMISC、IFF_ALLMULTI、IFF_MULTICAST)已变更,由于上层协议栈对于ndo_change_rx_flags和ndo_set_rx_mode的调用并不同意,硬件的相应设置变更被放到ndo_set_rx_mode实现; -
ndo_set_rx_mode
根据netdev->flags标记(IFF_PROMISC、IFF_ALLMULTI、IFF_MULTICAST),设置硬件混杂模式、单播模式、多播模式使能;
根据dev->uc设置硬件的单播地址列表,辅助函数netdev_for_each_uc_addr;
根据dev->mc设置硬件的多播地址列表,辅助函数netdev_for_each_mc_addr;
设置驱动私有的接收工作模式; -
ndo_set_mac_address
设置网卡的MAC地址,更新dev->dev_addr,并设置相应硬件寄存器;传递参数为struct sockaddr -
ndo_validate_addr
验证dev->dev_addr是否合法,对于ethernet网络,直接调用is_valid_ether_addr函数 -
ndo_do_ioctl
bond_dev设备相关的命令,驱动不理会;
PHY芯片的命令操作,直接使用generic_mii_ioctl函数phy_mii_ioctl函数;
net_bridge设备命令,驱动不理会;
PTP命令,驱动从ifr->ifr_data取出struct hwtstamp_config,根据其参数配置硬件的PTP时间戳功能;备注:config.flags必须为0,config.tx_type为HWTSTAMP_TX_OFF系列,config.rx_filter为HWTSTAMP_FILTER_NONE系列;
SIOCWANDEV命令,针对WLAN设备的。。。 -
ndo_set_config
驱动已经不再使用它啦~~ -
ndo_change_mtu
更改硬件的MTU值,注意检查硬件是否支持。。以及是否需要重新分配初始化描述符环以及接收缓存; -
ndo_neigh_setup
设置邻居子系统struct neigh_parms参数,驱动不理会 -
ndo_tx_timeout
此时多半MAC以及宕机了,当然实际处理应该因人而异,不一定非得进行一次完全复位初始化操作
保存现场快照;
关闭MAC和PHY;
复位并重新初始化MAC和PHY -
ndo_get_stats64
拷贝硬件统计计数到用户空间,注意使用的是__u64;对于1G或者更快的网卡,64位是必须的; -
ndo_get_stats
拷贝硬件统计计数到用户空间,注意使用的是__u32;可以直接拷贝netdev->stats;低速网卡使用此方法; -
ndo_vlan_rx_add_vid
如果网卡支持VLAN的VID过滤,则增加VID到过滤列表中; -
ndo_vlan_rx_kill_vid
如果网卡支持VLAN的VID过滤,则从过滤列表中删除对应的VID; -
ndo_poll_controller
使用查询方式代替中断方式处理网卡数据包收发事件,注意必须禁止中断,实际上直接调用中断函数,也可以直接调度NAPI去处理; -
ndo_netpoll_setup
-
ndo_netpoll_cleanup
只有虚拟网络设备在使用 -
ndo_set_vf_mac
设置虚拟网卡的MAC地址,第二个参数是VF,第三个参数是需要设置的MAC地址指针; -
ndo_set_vf_vlan
设置虚拟网卡的VLAN ID和User Priority,第二个参数代表虚拟功能号,第三个参数代表VLAN ID(1-4094),第四个参数代表User Priority(0-7)
VLAN TAG定义:
-
ndo_set_vf_tx_rate
设置虚拟网卡的传输速率,由于多个虚拟网卡共享一个物理端口,所以单个虚拟网卡的传输速率肯定小于标准值; -
ndo_set_vf_spoofchk
设置虚拟网卡的MAC/VLAN反欺诈过滤功能(传输通道); -
ndo_get_vf_config
获取虚拟网卡的参数,包括虚拟功能号、mac地址、传输速率、优先级Qos、欺诈使能标记,填入struct ifla_vf_info; -
ndo_set_vf_port
设置虚拟网卡的参数:可能包括IFLA_PORT_UNSPEC至IFLA_PORT_RESPONSE;当前只有cisco的enic驱动支持 -
ndo_get_vf_port
返回虚拟网卡的参数,放到传递的第三个参数(skb),可以调用辅助函数nla_put;当前只有cisco的enic驱动支持 -
ndo_setup_tc
设置网卡的流量分类控制,如果第二个参数为0 ,则调用netdev_reset_tc关闭流量分类控制功能,相应的禁止硬件功能;如果第二个参数不为0 ,则设置netdev_set_num_tc和netdev_set_prio_tc_map -
ndo_fcoe_enable
打开FCOE的卸货功能(offload),更新netdev->features,调用netdev_features_change,重新分配初始化传输接收环; -
ndo_fcoe_disable
关闭FCOE的卸货功能(offload),更新netdev->features,调用netdev_features_change,重新分配初始化传输接收环; -
ndo_fcoe_ddp_setup
-
ndo_fcoe_ddp_target
执行DDP(Direct Data Placement Protocol)相关的初始化操作,特别是struct scatterlist的DMA初始化; -
ndo_fcoe_ddp_done
DDP(Direct Data Placement Protocol)完成后的处理,释放资源供下次DDP请求; -
ndo_fcoe_get_hbainfo
返回硬件和驱动信息,填入struct netdev_fcoe_hbainfo; -
ndo_fcoe_get_wwn
返回驱动自定义的wwn值,传入的参数type可能是NETDEV_FCOE_WWNN或者NETDEV_FCOE_WWPN;
关于WWNN和WWPN的说明参考《WWNN和WWPN介绍》 -
ndo_rx_flow_steer
设置RFS硬件过滤器,关于RFS参考《Receive flow steering》; -
ndo_add_slave
创建虚拟网卡设备,驱动几乎不用; -
ndo_del_slave
删除虚拟网卡设备,驱动几乎不用; -
ndo_fix_features
检查并修正传递的netdev_features_t features是否满足硬件能力要求; -
ndo_set_features
更改网卡的netdev->features并设置硬件的相应能力; -
ndo_neigh_construct
构造邻居子系统struct neighbour,驱动几乎不用; -
ndo_neigh_destroy
反构造邻居子系统struct neighbour,驱动几乎不用; -
ndo_fdb_add
给指定地址添加FDB(forwarding database)实体; -
ndo_fdb_del
给指定地址删除FDB(forwarding database)实体; -
ndo_fdb_dump
导出FDB实体到skb中,同时更新idx,辅助函数包括netif_addr_lock_bh、nlmsg_populate_fdb、 -
ndo_bridge_setlink
设置网卡链路信息,从struct nlmsghdr获取设置信息; -
ndo_bridge_getlink
返回网卡链路信息,填入skb中; -
ndo_bridge_dellink
删除网卡链路信息; -
ndo_change_carrier
虚拟网络设备使用,模拟网络载波的开关;
在prob函数的最后一步骤是注册mii_bus,比如stmmac就是调用stmmac_mdio_register函数进行mii_bus注册,具体代码如下
- 该接口主要用于向系统中注册一个mii_bus device,并将该device注册至mdio_bus_class。
- 调用device_register,将该mii_bus的device成员注册至系统的device_kset中,并完成与mdio_bus_class的关联;
- 针对 phy address.范围为[0-31 ],且mii_bus 未设置忽略该phyid检测,则调用mdio_scan去搜索该phy addr下是否挂载了phydevice(通过mii_bus->read接口,获取phy id )),若获取到phy_id,则调用phy_device_register,将扫描到的phy_device注册至mdio_bus_type中。
int stmmac_mdio_register(struct net_device *ndev)
{
int err = 0;
struct mii_bus *new_bus;
struct stmmac_priv *priv = netdev_priv(ndev);
struct stmmac_mdio_bus_data *mdio_bus_data = priv->plat->mdio_bus_data;
struct device_node *mdio_node = priv->plat->mdio_node;
struct device *dev = ndev->dev.parent;
int addr, found, max_addr;
if (!mdio_bus_data)
return 0;
new_bus = mdiobus_alloc();
if (!new_bus)
return -ENOMEM;
if (mdio_bus_data->irqs)
memcpy(new_bus->irq, mdio_bus_data->irqs, sizeof(new_bus->irq));
new_bus->name = "stmmac";
if (priv->plat->has_xgmac) {
new_bus->read = &stmmac_xgmac2_mdio_read;
new_bus->write = &stmmac_xgmac2_mdio_write;
max_addr = MII_XGMAC_MAX_C22ADDR + 1;
if (priv->plat->phy_addr > MII_XGMAC_MAX_C22ADDR)
dev_err(dev, "Unsupported phy_addr (max=%d)n",
MII_XGMAC_MAX_C22ADDR);
} else {
new_bus->read = &stmmac_mdio_read;
new_bus->write = &stmmac_mdio_write;
max_addr = PHY_MAX_ADDR;
}
if (mdio_bus_data->has_xpcs) {
priv->hw->xpcs = mdio_xpcs_get_ops();
if (!priv->hw->xpcs) {
err = -ENODEV;
goto bus_register_fail;
}
}
if (mdio_bus_data->needs_reset)
new_bus->reset = &stmmac_mdio_reset;
snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x",
new_bus->name, priv->plat->bus_id);
new_bus->priv = ndev;
new_bus->phy_mask = mdio_bus_data->phy_mask;
new_bus->parent = priv->device;
err = of_mdiobus_register(new_bus, mdio_node);
if (err != 0) {
dev_err(dev, "Cannot register the MDIO busn");
goto bus_register_fail;
}
if (priv->plat->has_xgmac)
stmmac_xgmac2_mdio_read(new_bus, 0, MII_ADDR_C45);
if (priv->plat->phy_node || mdio_node)
goto bus_register_done;
found = 0;
for (addr = 0; addr < max_addr; addr++) {
struct phy_device *phydev = mdiobus_get_phy(new_bus, addr);
if (!phydev)
continue;
if (!mdio_bus_data->irqs &&
(mdio_bus_data->probed_phy_irq > 0)) {
new_bus->irq[addr] = mdio_bus_data->probed_phy_irq;
phydev->irq = mdio_bus_data->probed_phy_irq;
}
if (priv->plat->phy_addr == -1)
priv->plat->phy_addr = addr;
phy_attached_info(phydev);
found = 1;
}
if (priv->hw->xpcs) {
struct mdio_xpcs_args *xpcs = &priv->hw->xpcs_args;
int ret, mode = priv->plat->phy_interface;
max_addr = PHY_MAX_ADDR;
xpcs->bus = new_bus;
for (addr = 0; addr < max_addr; addr++) {
xpcs->addr = addr;
ret = stmmac_xpcs_probe(priv, xpcs, mode);
if (!ret) {
found = 1;
break;
}
}
}
if (!found && !mdio_node) {
dev_warn(dev, "No PHY foundn");
mdiobus_unregister(new_bus);
mdiobus_free(new_bus);
return -ENODEV;
}
bus_register_done:
priv->mii = new_bus;
return 0;
bus_register_fail:
mdiobus_free(new_bus);
return err;
}
返回总目录



