栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux ARM平台开发系列讲解(网络篇)1.6 STMMAC网络驱动框架分析

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux ARM平台开发系列讲解(网络篇)1.6 STMMAC网络驱动框架分析

1. 网路硬件组成拓扑

如下图,一般网络硬件都由三部分组成,分别为内部MAC + 外部PHY + RJ45 ,共同组成了一个完整的嵌入式网络接口硬件。MAC跟PHY的通讯通过MDIO总线 和 MII/RMII/GMII/RGMII等接口完成,其中驱动程序可以通过 MDIO 总线访问 PHY 芯片的任意一个寄存器,来完成对PHY的配置,而网络数据传输则通过 MII/RMII/GMII/RGMII等接口完成。

2. stmmac网络驱动框架

下图为stmmac网络驱动框架,主要是注册流程和数据传输机制,可以根据这个拓扑图去阅读代码,这个框图很重要,后续我会分别进行讲解

3. stmmac网络驱动框架
  • 源码目录: stmmac所有的源代码都在drivers/net/ethernet/stmicro/stmmac目录下
3.1 链表设备通知事件
  • Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notificationchain)。
  • 通知链只能用在各个子系统之间,而不能在内核和用户空间进行事件的通知。组成内核的核心系统代码均位于kernel目录下,通知链表位于kernel/notifier.c中,对应的头文件为include/linux/notifier.h。通知链表机制并不复杂,实现它的代码只有区区几百行。
  • 事件通知链表是一个事件处理函数的列表,每个通知链都与某个或某些事件有关,当特定的事件发生时,就调用相应的事件通知链中的回调函数,进行相应的处理。
3.2 创建和删除网络设备通知事件
  • 源码路径: 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这个入口函数
3.4 网络GAMC入口函数s32cc_dwmac_probe解析
  • 获取设备树硬件资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
  • 根据设备树信息,初始化gmac状态
    ret = s32cc_gmac_init(pdev, gmac);
  • 初始化工作队列,网络硬件设备,这里调用的是stmmac_dvr_probe函数,起作用就是初始化工作队列,处理各种事件;初始化gamc硬件,填充gamc的回调函数,比如设备打开关闭和接收发送等
3.5 网络设备struct net_device_ops结构体分析

这个的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
    虚拟网络设备使用,模拟网络载波的开关;

3.6 MII_BUS的注册

在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;
}

返回总目录

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/962884.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号