Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
许久没有更新,的确是最近有点懈怠了。没有任何借口,接受所有的批评。接下来无论如何也要坚持下去,不管处于什么境地、什么原因,没有任何借口。一时的中断可能都要重新开始学,教训啊。万里长征也算走过了八千里路,接下来一鼓作气,每天一更。
先来一张本章的思维导图:
3.fec_netdev_ops操作集
fec_probe 函数设置了网卡驱动的 net_dev_ops 操作集为 fec_netdev_ops,如下:
1 static const struct net_device_ops fec_netdev_ops = {
2 .ndo_open = fec_enet_open,
3 .ndo_stop = fec_enet_close,
4 .ndo_start_xmit = fec_enet_start_xmit,
5 .ndo_select_queue = fec_enet_select_queue,
6 .ndo_set_rx_mode = set_multicast_list,
7 .ndo_change_mtu = eth_change_mtu,
8 .ndo_validate_addr = eth_validate_addr,
9 .ndo_tx_timeout = fec_timeout,
10 .ndo_set_mac_address = fec_set_mac_address,
11 .ndo_do_ioctl = fec_enet_ioctl,
12 #ifdef CONFIG_NET_POLL_CONTROLLER
13 .ndo_poll_controller = fec_poll_controller,
14 #endif
1)打开一个网卡的时候 fec_enet_open 函数就会执行
ret = fec_enet_clk_enable(ndev, true);//调用 fec_enet_clk_enable 函数使能 enet 时钟。
ret = fec_enet_alloc_buffers(ndev);/*调用 fec_enet_alloc_buffers 函数申请环形缓冲区 buffer,此函数里面会调用
fec_enet_alloc_rxq_buffers 和 fec_enet_alloc_txq_buffers 这两个函数分别实现发送队列和接收队
列缓冲区的申请。*/
fec_restart(ndev);/*重启网络,一般连接状态改变、传输超时或者配置网络的时候都会调用 fec_restart
函数。*/
ret = fec_enet_mii_probe(ndev);/* 重启网络,一般连接状态改变、传输超时或者配置网络的时候都会调用 fec_restart
函数。*/
29 napi_enable(&fep->napi);//调用 napi_enable 函数使能 NAPI 调度
30 phy_start(fep->phy_dev);//调用 phy_start 函数开启 PHY 设备。
31 netif_tx_start_all_queues(ndev);//调用 netif_tx_start_all_queues 函数来激活发送队列。
2)关闭网卡的时候 fec_enet_close 函数就会执行
phy_stop(fep->phy_dev);//调用 phy_stop 函数停止 PHY 设备。
napi_disable(&fep->napi);//调用 napi_disable 函数关闭 NAPI 调度。
netif_tx_disable(ndev);//调用 netif_tx_disable 函数关闭 NAPI 的发送队列。
1fec_stop(ndev);//调用 fec_stop 函数关闭 I.MX6ULL 的 ENET 外设。
phy_disconnect(fep->phy_dev);//调用 phy_disconnect 函数断开与 PHY 设备的连接。
fec_enet_clk_enable(ndev, false);//调用 fec_enet_clk_enable 函数关闭 ENET 外设时钟。
fec_enet_free_buffers(ndev);//调用 fec_enet_free_buffers 函数释放发送和接收的环形缓冲区内存。
3)I.MX6ULL 的网络数据发送是通过 fec_enet_start_xmit 函数来完成的,这个函数将上层传递过来的 sk_buff 中的数据通过硬件发送出去.
static netdev_tx_t fec_enet_start_xmit(struct sk_buff *skb,
struct net_device *ndev)
/*
skb 就是上层应用传递下来的要发送的网络数据
ndev 就是要发送数据的设备
*/
if (skb_is_gso(skb))/* 判断 skb 是否为 GSO(Generic Segmentation Offload),如果是 GSO 的话就通过fec_enet_txq_submit_tso 函数发送,如果不是的话就通过 fec_enet_txq_submit_skb 发送。*/
ret = fec_enet_txq_submit_tso(txq, skb, ndev);
else
ret = fec_enet_txq_submit_skb(txq, skb, ndev);
if (ret)
return ret;
entries_free = fec_enet_get_free_txdesc_num(fep, txq);//获取剩余的发送描述符数量
if (entries_free <= txq->tx_stop_threshold)
netif_tx_stop_queue(nq);/*如果剩余的发送描述符的数量小于设置的阈值(tx_stop_threshold)的话就调用函数netif_tx_stop_queue 来暂停发送,通过暂停发送来通知应用层停止向网络发送 skb,发送中断中
会重新开启的。 */
4)中断服务函数为 fec_enet_interrupt
int_events = readl(fep->hwp + FEC_IEVENT);//读取 NENT 的中断状态寄存器 EIR,获取中断状态,
writel(int_events, fep->hwp + FEC_IEVENT);//清除中断状态寄存器。
fec_enet_collect_events(fep, int_events);//统计都发生了哪些中断
if (napi_schedule_prep(&fep->napi)) {//调用 napi_schedule_prep 函数检查 NAPI 是否可以进行调度
/* Disable the NAPI interrupts */
writel(FEC_ENET_MII, fep->hwp + FEC_IMASK);/*如果使能了相关中断就要先关闭这些中断,向 EIMR 寄存器的 bit23 写 1 即可关闭相关中断。*/
__napi_schedule(&fep->napi);/*调用__napi_schedule 函数来启动 NAPI 调度,这个时候 napi 的 poll 函数就会执行,在本网络驱动中就是 fec_enet_rx_napi 函数。*/
}
具体的网络数据收发是在 NAPI 的 poll 函数中完成的.
5)fec_enet_interrupt 中断服务函数
fec_enet_init 函数初始化网络的时候会调用 netif_napi_add 来设置 NAPI 的 poll 函数为
fec_enet_rx_napi。
1 static int fec_enet_rx_napi(struct napi_struct *napi, int budget)
2 {
3 struct net_device *ndev = napi->dev;
4 struct fec_enet_private *fep = netdev_priv(ndev);
5 int pkts;
6
7 pkts = fec_enet_rx(ndev, budget);//调用 fec_enet_rx 函数进行真正的数据接收。
8
9 fec_enet_tx(ndev);//调用 fec_enet_tx 函数进行数据发送。
10
11 if (pkts < budget) {
12 napi_complete(napi);//调用 napi_complete 函数来宣布一次轮询结束,
13 writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);//设置 ENET 的 EIMR 寄存器,重新使能中断。
14 }
15 return pkts;
16 }
4.Linux内核PHY子系统与MDIO总线简析
1)PHY 设备
使用 phy_device 结构体来表示 PHY 设备,结构体定义如下:
1 struct phy_device {
2 /* Information about the PHY type */
3 /* And management functions */
4 struct phy_driver *drv; /* PHY 设备驱动 */
5 struct mii_bus *bus; /* 对应的 MII 总线 */
6 struct device dev; /* 设备文件 */
7 u32 phy_id; /* PHY ID */
8 9
struct phy_c45_device_ids c45_ids;
10 bool is_c45;
11 bool is_internal;
12 bool has_fixups;
13 bool suspended;
14
15 enum phy_state state; /* PHY 状态 */
16 u32 dev_flags;
17 phy_interface_t interface; /* PHY 接口 */
18
19 /* Bus address of the PHY (0-31) */
20 int addr; /* PHY 地址(0~31) */
21
22 /*
23 * forced speed & duplex (no autoneg)
24 * partner speed & duplex & pause (autoneg)
25 */
26 int speed; /* 速度 */
27 int duplex; /* 双共模式 */
28 int pause;
29 int asym_pause;
30
31 /* The most recently read link state */
32 int link;
33
34 /* Enabled Interrupts */
35 u32 interrupts; /* 中断使能标志 */
36
37 /* Union of PHY and Attached devices' supported modes */
38 /* See mii.h for more info */
39 u32 supported;
40 u32 advertising;
41 u32 lp_advertising;
42 int autoneg;
43 int link_timeout;
44
45 /*
46 * Interrupt number for this PHY
47 * -1 means no interrupt
48 */
49 int irq; /* 中断号 */
50
51 /* private data pointer */
52 /* For use by PHYs to maintain extra state */
53 void *priv; /* 私有数据 */
54
55 /* Interrupt and Polling infrastructure */
56 struct work_struct phy_queue;
57 struct delayed_work state_queue;
58 atomic_t irq_disable;
59 struct mutex lock;
60 struct net_device *attached_dev; /* PHY 芯片对应的网络设备 */
61 void (*adjust_link)(struct net_device *dev);
62 };
一个 PHY 设备对应一个 phy_device 实例,然后需要向 Linux 内核注册这个实例。使用
phy_device_register 函数完成 PHY 设备的注册:
int phy_device_register(struct phy_device *phy)//phy: 需要注册的 PHY 设备。
PHY 设备的注册过程一般是先调用 get_phy_device 函数获取 PHY 设备.
1 struct phy_device *get_phy_device(struct mii_bus *bus, int addr,
bool is_c45)
2 {
3 struct phy_c45_device_ids c45_ids = {0};
4 u32 phy_id = 0;
5 int r;
6
7 r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);/*调用 get_phy_id 函数获取 PHY ID,也就是读取 PHY 芯片的那两个 ID 寄存器,得到 PHY 芯片 ID 信息。*/
8 if (r)
9 return ERR_PTR(r);
10
11 /* If the phy_id is mostly Fs, there is no device there */
12 if ((phy_id & 0x1fffffff) == 0x1fffffff)
13 return NULL;
14
15 return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);/*调用 phy_device_create 函数创建 phy_device,此函数先申请 phy_device 内存,然后初始化 phy_device 的各个结构体成员,最终返回创建好的 phy_device。 phy_device_register 函数注册的就是这个创建好的 phy_device。*/
16 }
2)PHY驱动
PHY 驱动使用结构体 phy_driver 表示.
①、注册 PHY 驱动 --- phy_driver_register 函数
注册phy驱动的时候会设置驱动的总线为mdio_bus_type,也就是MDIO总线.
int phy_driver_register(struct phy_driver *new_driver)
②、连续注册多个 PHY 驱动 --- phy_drivers_register
int phy_drivers_register(struct phy_driver *new_driver, int n)
③、卸载 PHY 驱动
void phy_driver_unregister(struct phy_driver *drv)
3)MDIO 总线
设备和驱动就是 phy_device 和phy_driver。总线就是 MDIO 总线
MDIO 总线最主要的工作就是匹配 PHY 设备和 PHY 驱动。
if (phydrv->match_phy_device)
return phydrv->match_phy_device(phydev);
/*
检查 PHY 驱动有没有提供匹配函数 match_phy_device,如果有的话就直接调
用 PHY 驱动提供的匹配函数完成与设备的匹配。
*/
对比 PHY 驱动和 PHY 设备中的 phy_id 是否一致,这里需要与PHY 驱动里面的 phy_id_mask 进行与运算,如果结果一致的话就说明驱动和设备匹配。
4)通用 PHY 驱动 --- Generic PHY
rc = phy_drivers_register(genphy_driver,
ARRAY_SIZE(genphy_driver));
5)LAN8720A 驱动
配置路径如下:
-> Device Drivers
-> Network device support
-> PHY Device support and infrastructure
-> Drivers for SMSC PHYs
93 }, {
94 .phy_id = 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */
95 .phy_id_mask = 0xfffffff0,
96 .name = "SMSC LAN8710/LAN8720",
97
98 .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause
99 | SUPPORTED_Asym_Pause),
100 .flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
101
102 /* basic functions */
103 .config_aneg = genphy_config_aneg,
104 .read_status = lan87xx_read_status,
105 .config_init = smsc_phy_config_init,
106 .soft_reset = smsc_phy_reset,
107
108 /* IRQ related */
109 .ack_interrupt = smsc_phy_ack_interrupt,
110 .config_intr = smsc_phy_config_intr,
111
112 .suspend = genphy_suspend,
113 .resume = genphy_resume,
114
115 .driver = { .owner = THIS_MODULE, }
116 } };
五、网络驱动实验测试
1.LAN8720 PHY驱动测试
2.通用PHY驱动测试
3.DHCP功能配置
udhcpc 命令
在 busybox 源码中找到 examples/udhcp/simple.script,将其拷贝到开发板/usr/share/udhcpc 目录下(如果没有的话请自行创建此目录),拷贝完成以后将根文件系统下的 simple.script 并且重命名为 default.script,命令如下:
cd busybox-1.29.0/examples/udhcp
cp simple.script /home/zuozhongkai/linux/nfs/rootfs/usr/share/udhcpc/default.script //拷贝并重
命名
六、单网卡使用
1.只使用ENET2网卡
1)屏蔽或删除掉 fec2 节点内容
首先找到 fec1 节点,然后将其中的 status 属性改为“disabled”即可。
1 &fec1 {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_enet1
4 &pinctrl_enet1_reset>;
5 phy-mode = "rmii";
6 phy-handle = <ðphy0>;
7 phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
8 phy-reset-duration = <200>;
9 status = "disabled";
10 };
2)修改 ENET1 对应的 fec1 节点信息。
11 mdio {
12 #address-cells = <1>;
13 #size-cells = <0>;
14
15 ethphy0: ethernet-phy@0 {
16 compatible = "ethernet-phy-ieee802.3-c22";
17 reg = <0>;
18 };
3)屏蔽或删除掉 ENET2 对应的 pinctrl 节点
4)在 ENET1 网卡对应的 pinctrl 节点中添加 MDIO 和 MDC 引脚配置
MX6UL_PAD_GPIO1_IO07__ENET1_MDC 0x1b0b0
MX6UL_PAD_GPIO1_IO06__ENET1_MDIO 0x1b0b0
2.只使用ENET1网卡
总结:
关于 Linux 的网络驱动,整体比较复杂,但是实际使用起来确实非常简单的,尤其是对这种内置 MAC+外置 PHY 的网络方案而言,几乎不需要我们修改驱动,因为内核已经继承了通用 PHY 驱动了。
本笔记为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。