以太网驱动的流程浅析(一)-Ifconfig主要流程
作者:heaven 发布于:2019-12-6 15:50 分类:Linux内核分析
Author:张昺华
Email:920052390@qq.com
Time:2019年3月23日星期六
大家好,我叫张昺华,中间那个字和“饼”字一个读音。
很喜欢一群人在研究技术,一起做有意思的东西,一起分享技术带给我们的快乐,也希望中国有更多的人热爱技术,喜欢一起研究、分享技术,然后可以一起用我们的技术来做一些好玩的东西,可以为这个社会创造一些东西来改善人们的生活。
如下是本人调试过程中的一点经验分享,以太网驱动架构毕竟涉及的东西太多,如下仅仅是针对加载流程和围绕这个问题产生的分析过程和驱动加载流程部分,并不涉及以太网协议层的数据流程分析。
【硬件环境】 Imx6ul
【Linux kernel版本】 Linux4.1.15
【以太网phy】 Realtek8201f
1. 问题描述
【问题】
机器通过usb方式下载了mac地址后,发现以太网无法正常使用,敲命令 ifconfig eth0 up出现:ifconfig: SIOCSIFFLAGS: No such device,而对于没有下载以太网mac address的机器表现均正常。调试过程中发现在以太网控制器代码中加入一些printk,不正常的机器又正常了,打印的位置不同,机器的以太网有时会正常,有时会异常,十分诡异。
2. 原因分析
【根本原因】
reset时序问题导致,phy reset的时间不满足时序要求。如下图,如果硬件接了reset引脚,应满足时序要求在reset保持10ms有效电平后,还必须维持至少150ms才可以访问phy register,也就是reset要在B点之后才可以正常通过MDC/MDIO来访问phy register。如果是不使用硬件reset,使用软件reset方式,那也要至少在A点,也就是在reset维持10ms有效电平后,再维持3.5个clk才能正常访问phy register。
那为什么下载了mac地址后才异常呢?不下载的又正常呢?
【原因分析】
freescale控制器获取mac address流程如下:
1)模块化参数设置,如果没有跳到步骤2;
2)device tree中设置,如果没有跳到步骤3;
3)from flash / fuse / via platform data,如果没有跳到步骤4;
4)FEC mac registers set by bootloader===》即靠usb方式下载mac address ,如果没有跳到步骤5;
5)靠kernel算一个随机数mac address出来,然后写入mac
那为什么下载了mac地址后才异常呢?
下了mac后,会执行步骤4,不会执行步骤5,此时目前的代码不满足150ms的时序要求,无法访问phy register,
导致phy_id获取不到,因此phy_device也不会创建
那为什么不下载的又正常呢?
不下载mac address,会执行步骤5 ,步骤5中调用了函数eth_hw_addr_random
刚好满足了150ms的时序要求,所以才可以正常
/* * 4) FEC mac registers set by bootloader */ if (!is_valid_ether_addr(iap)) { *((__be32 *) &tmpaddr[0]) = cpu_to_be32(readl(fep->hwp + FEC_ADDR_LOW)); *((__be16 *) &tmpaddr[4]) = cpu_to_be16(readl(fep->hwp + FEC_ADDR_HIGH) >> 16); iap = &tmpaddr[0]; } /* * 5) random mac address */ if (!is_valid_ether_addr(iap)) { /* Report it and use a random ethernet address instead */ netdev_err(ndev, "Invalid MAC address: %pM\n", iap); eth_hw_addr_random(ndev); netdev_info(ndev, "Using random MAC address: %pM\n", ndev->dev_addr); return; }
跟入代码eth_hw_addr_random看下
/** * eth_hw_addr_random - Generate software assigned random Ethernet and * set device flag * @dev: pointer to net_device structure * * Generate a random Ethernet address (MAC) to be used by a net device * and set addr_assign_type so the state can be read by sysfs and be * used by userspace. */ static inline void eth_hw_addr_random(struct net_device *dev) { dev->addr_assign_type = NET_ADDR_RANDOM; eth_random_addr(dev->dev_addr); }
继续看:
/** * eth_random_addr - Generate software assigned random Ethernet address * @addr: Pointer to a six-byte array containing the Ethernet address * * Generate a random Ethernet address (MAC) that is not multicast * and has the local assigned bit set. */ static inline void eth_random_addr(u8 *addr) { get_random_bytes(addr, ETH_ALEN); addr[0] &= 0xfe; /* clear multicast bit */ addr[0] |= 0x02; /* set local assignment bit (IEEE802) */ }
最终调用了kernel提供的获取随机数的一个函数,这块代码比较多就不继续追下去了。
/* * This function is the exported kernel interface. It returns some * number of good random numbers, suitable for key generation, seeding * TCP sequence numbers, etc. It does not rely on the hardware random * number generator. For random bytes direct from the hardware RNG * (when available), use get_random_bytes_arch(). */ void get_random_bytes(void *buf, int nbytes) { #if DEBUG_RANDOM_BOOT > 0 if (unlikely(nonblocking_pool.initialized == 0)) printk(KERN_NOTICE "random: %pF get_random_bytes called " "with %d bits of entropy available\n", (void *) _RET_IP_, nonblocking_pool.entropy_total); #endif trace_get_random_bytes(nbytes, _RET_IP_); extract_entropy(&nonblocking_pool, buf, nbytes, 0, 0); } EXPORT_SYMBOL(get_random_bytes);
所以这块步骤五的代码刚刚好好在这个硬件条件下,恰巧满足了150ms的reset时序要求,所以以太网才可以正常。
3. 以太网流程分析跟踪
3.1 Ifconfig主要流程
回归主题,根据这个ifconfig失败的现象,我们追踪一下code:
ifconfig: SIOCSIFFLAGS: No such device,既然出现了这个问题log,我们就从应用层的log入手,首先我们使用strace命令来追踪下系统调用,以便于我们追踪内核代码实现。
strace ifconfig eth0 up跟踪一下
可以发现主要是ioctl的操作,SIOCSIFFLAGS,然后我们需要了解下这个宏的意思,说白了就是设置各种flag,靠ioctl第三个参数把所需要的动作flag传入,比如说此时要对eth0进行up动作,那么就传入IFF_UP,例如:
struct ifreq ifr;
我们看这些主要是想知道为什么会打印这个log:
ifconfig: SIOCSIFFLAGS: No such device
那么内核中又是对ioctl做了什么动作呢?因为strace命令让我们知道了系统调用调用函数,我们可以在kernel中直接搜索SIOCSIFFLAGS,或者去以太网驱动net目录下直接搜索更快。最终我搜到了,路径是:net/ipv4/devinet.c
我们可以看到内核的宏定义:
/* Socket configuration controls. */ #define SIOCGIFNAME 0x8910 /* get iface name */ #define SIOCSIFLINK 0x8911 /* set iface channel */ #define SIOCGIFCONF 0x8912 /* get iface list */ #define SIOCGIFFLAGS 0x8913 /* get flags */ #define SIOCSIFFLAGS 0x8914 /* set flags */ #define SIOCGIFADDR 0x8915 /* get PA address */ #define SIOCSIFADDR 0x8916 /* set PA address */ #define SIOCGIFDSTADDR 0x8917 /* get remote PA address */ #define SIOCSIFDSTADDR 0x8918 /* set remote PA address */ #define SIOCGIFBRDADDR 0x8919 /* get broadcast PA address */ #define SIOCSIFBRDADDR 0x891a /* set broadcast PA address */ #define SIOCGIFNETMASK 0x891b /* get network PA mask */ #define SIOCSIFNETMASK 0x891c /* set network PA mask */ #define SIOCGIFMETRIC 0x891d /* get metric */ #define SIOCSIFMETRIC 0x891e /* set metric */ #define SIOCGIFMEM 0x891f /* get memory address (BSD) */ #define SIOCSIFMEM 0x8920 /* set memory address (BSD) */ #define SIOCGIFMTU 0x8921 /* get MTU size */ #define SIOCSIFMTU 0x8922 /* set MTU size */ #define SIOCSIFNAME 0x8923 /* set interface name */ #define SIOCSIFHWADDR 0x8924 /* set hardware address */ #define SIOCGIFENCAP 0x8925 /* get/set encapsulations */ #define SIOCSIFENCAP 0x8926 #define SIOCGIFHWADDR 0x8927 /* Get hardware address */ #define SIOCGIFSLAVE 0x8929 /* Driver slaving support */ #define SIOCSIFSLAVE 0x8930 #define SIOCADDMULTI 0x8931 /* Multicast address lists */ #define SIOCDELMULTI 0x8932 #define SIOCGIFINDEX 0x8933 /* name -> if_index mapping */ #define SIOGIFINDEX SIOCGIFINDEX /* misprint compatibility :-) */ #define SIOCSIFPFLAGS 0x8934 /* set/get extended flags set */ #define SIOCGIFPFLAGS 0x8935 #define SIOCDIFADDR 0x8936 /* delete PA address */ #define SIOCSIFHWBROADCAST 0x8937 /* set hardware broadcast addr */ #define SIOCGIFCOUNT 0x8938 /* get number of devices */ #define SIOCGIFBR 0x8940 /* Bridging support */ #define SIOCSIFBR 0x8941 /* Set bridging options */ #define SIOCGIFTXQLEN 0x8942 /* Get the tx queue length */ #define SIOCSIFTXQLEN 0x8943 /* Set the tx queue length */
查看devinet.c的代码,我们找到了那个宏,也就是做devinet_ioctl函数中,这也就是应用层的ioctl最终的实现函数,然后我们在里面加一些打印,
通过打印结果我们可以确认是这个函数devinet_ioctl为应用层的ioctl的实现函数,因为你在kernel中搜SIOCSIFFLAGS宏的话会有很多地方出现的,所以我们需要确认我们找的函数没问题:
看到这里返回值ret是-19,那么我们继续顺着追踪下去,上代码:
net/core/dev.c
/** * dev_change_flags - change device settings * @dev: device * @flags: device state flags * * Change settings on device based state flags. The flags are * in the userspace exported format. */ int dev_change_flags(struct net_device *dev, unsigned int flags) { int ret; unsigned int changes, old_flags = dev->flags, old_gflags = dev->gflags; ret = __dev_change_flags(dev, flags); if (ret < 0) return ret; changes = (old_flags ^ dev->flags) | (old_gflags ^ dev->gflags); __dev_notify_flags(dev, old_flags, changes); return ret; } EXPORT_SYMBOL(dev_change_flags);
继续追踪:net/core/dev.c
int __dev_change_flags(struct net_device *dev, unsigned int flags) { unsigned int old_flags = dev->flags; int ret; ASSERT_RTNL(); /* * Set the flags on our device. */ dev->flags = (flags & (IFF_DEBUG | IFF_NOTRAILERS | IFF_NOARP | IFF_DYNAMIC | IFF_MULTICAST | IFF_PORTSEL | IFF_AUTOMEDIA)) | (dev->flags & (IFF_UP | IFF_VOLATILE | IFF_PROMISC | IFF_ALLMULTI)); /* * Load in the correct multicast list now the flags have changed. */ if ((old_flags ^ flags) & IFF_MULTICAST) dev_change_rx_flags(dev, IFF_MULTICAST); dev_set_rx_mode(dev); /* * Have we downed the interface. We handle IFF_UP ourselves * according to user attempts to set it, rather than blindly * setting it. */ ret = 0; if ((old_flags ^ flags) & IFF_UP) ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev); if ((flags ^ dev->gflags) & IFF_PROMISC) { int inc = (flags & IFF_PROMISC) ? 1 : -1; unsigned int old_flags = dev->flags; dev->gflags ^= IFF_PROMISC; if (__dev_set_promiscuity(dev, inc, false) >= 0) if (dev->flags != old_flags) dev_set_rx_mode(dev); } /* NOTE: order of synchronization of IFF_PROMISC and IFF_ALLMULTI is important. Some (broken) drivers set IFF_PROMISC, when IFF_ALLMULTI is requested not asking us and not reporting. */ if ((flags ^ dev->gflags) & IFF_ALLMULTI) { int inc = (flags & IFF_ALLMULTI) ? 1 : -1; dev->gflags ^= IFF_ALLMULTI; __dev_set_allmulti(dev, inc, false); } return ret; }
因此我们可以看到返回值-19就是如下代码产生的
/* * Have we downed the interface. We handle IFF_UP ourselves * according to user attempts to set it, rather than blindly * setting it. */ ret = 0; if ((old_flags ^ flags) & IFF_UP) ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev);
因此我们需要追踪__dev_open函数,继续看代码:
static int __dev_open(struct net_device *dev) { const struct net_device_ops *ops = dev->netdev_ops; int ret; ASSERT_RTNL(); if (!netif_device_present(dev)) return -ENODEV; /* Block netpoll from trying to do any rx path servicing. * If we don't do this there is a chance ndo_poll_controller * or ndo_poll may be running while we open the device */ netpoll_poll_disable(dev); ret = call_netdevice_notifiers(NETDEV_PRE_UP, dev); ret = notifier_to_errno(ret); if (ret) return ret; set_bit(__LINK_STATE_START, &dev->state); if (ops->ndo_validate_addr) ret = ops->ndo_validate_addr(dev); if (!ret && ops->ndo_open) ret = ops->ndo_open(dev); netpoll_poll_enable(dev); if (ret) clear_bit(__LINK_STATE_START, &dev->state); else { dev->flags |= IFF_UP; dev_set_rx_mode(dev); dev_activate(dev); add_device_randomness(dev->dev_addr, dev->addr_len); } return ret; }
通过调试,比如说加打印,或者是经验我们可以推断出是这里返回的-19,那么这个ndo_open又是在哪里回调的呢?
if (!ret && ops->ndo_open) ret = ops->ndo_open(dev);
我们可以看到ops这个结构的结构体
struct net_device *dev
const struct net_device_ops *ops = dev->netdev_ops;
/* * This structure defines the management hooks for network devices. * The following hooks can be defined; unless noted otherwise, they are * optional and can be filled with a null pointer. * * int (*ndo_init)(struct net_device *dev); * This function is called once when network device is registered. * The network device can use this to any late stage initializaton * or semantic validattion. It can fail with an error code which will * be propogated back to register_netdev * * void (*ndo_uninit)(struct net_device *dev); * This function is called when device is unregistered or when registration * fails. It is not called if init fails. * * int (*ndo_open)(struct net_device *dev); * This function is called when network device transistions to the up * state. * * int (*ndo_stop)(struct net_device *dev); * This function is called when network device transistions to the down * state. struct net_device_ops { int (*ndo_init)(struct net_device *dev); void (*ndo_uninit)(struct net_device *dev); int (*ndo_open)(struct net_device *dev); int (*ndo_stop)(struct net_device *dev); netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev); u16 (*ndo_select_queue)(struct net_device *dev,
这里熟悉驱动的朋友应该可以猜到这在在freescale的以太网控制器驱动中一定有它的实现
net_device_ops就是kernel提供给drvier操作net_device的一些操作方法,具体实现自然由相应厂商的driver自己去实现。
路径:drivers/net/Ethernet/freescale/fec_main.c
static const struct net_device_ops fec_netdev_ops = { .ndo_open = fec_enet_open, .ndo_stop = fec_enet_close, .ndo_start_xmit = fec_enet_start_xmit, .ndo_select_queue = fec_enet_select_queue, .ndo_set_rx_mode = set_multicast_list, .ndo_change_mtu = eth_change_mtu, .ndo_validate_addr = eth_validate_addr, .ndo_tx_timeout = fec_timeout, .ndo_set_mac_address = fec_set_mac_address, .ndo_do_ioctl = fec_enet_ioctl, #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = fec_poll_controller, #endif .ndo_set_features = fec_set_features, };
我们可以在这个fec_enet_open函数中加入dump_stack来看下整个调用情况
我们打出kernel的dump_stack信息来看:
这个调用过程就是应用层ioctl一直到kernel最底层fec_enet_open的过程。
应用代码这样:
总体流程:kill() -> kill.S -> swi陷入内核态 -> 从sys_call_table查看到sys_kill -> ret_fast_syscall -> 回到用户态执行kill()下一行代码
Ioctl《==ret_fast_syscall 《==SyS_ioctl《==do_vfs_ioctl《==vfs_ioctl《==sock_ioctl《==
devinet_ioctl《==dev_change_flags《==__dev_change_flags《==__dev_open《==fec_enet_open
我附上每个函数的代码:
如果大家想看系统调用流程的话,参考这篇,我就不做这块的说明了:
Linux系统调用(syscall)原理
http://gityuan.com/2016/05/21/syscall/
https://www.cnblogs.com/cslunatic/p/3655970.html
4. 网址分享
http://stackoverflow.com/questions/5308090/set-ip-address-using-siocsifaddr-ioctl
https://lkml.org/lkml/2017/2/3/396
linux PHY驱动
http://www.latelee.org/programming-under-linux/linux-phy-driver.html
Linux PHY几个状态的跟踪
http://www.latelee.org/programming-under-linux/linux-phy-state.html
第十六章PHY -基于Linux3.10
https://blog.csdn.net/shichaog/article/details/44682931

评论:
2021-01-18 14:03
这个等待不只是以太网需要注意,pcie驱动也要注意,pcie host在启动的时候,也会发reset信号, 如果硬件有接reset,也会硬件复位外设,如果reset之后立马读config寄存器,也会有概率读不到设备,导致pcie设备无法正常识别。(pcie需要host先起来,然后探测到设备,加入到pcie总线,开启相应的时钟,如果没有探测到设备,就会关闭时钟,pcie总线上也不会有设备,后期pcie device驱动加载的时候就发现不了设备)
2019-12-31 10:56
2020-01-06 14:02
1)模块化参数设置,如果没有跳到步骤2;
2)device tree中设置,如果没有跳到步骤3;
3)from flash / fuse / via platform data,如果没有跳到步骤4;
4)FEC mac registers set by bootloader===》即靠usb方式下载mac address ,如果没有跳到步骤5;
5)靠kernel算一个随机数mac address出来,然后写入mac
所以你们可以选择123步骤中设置好mac地址即可,就可以永久改写mac地址,就不需要usb的方式了,或者你们非想要实现ifconfig的方式来设置,可以修改你们自身的driver来实现这个功能
功能
最新评论
文章分类
随机文章
文章存档
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2020-03-07 10:48