大家好,欢迎来到IT知识分享网。
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 以太网设备的初始化
下面以 U-Boot 2016.05
代码下 ARMv7
架构代码为例,来说明 U-Boot 以太网设备的初始化过程。
/* arch/arm/lib/vector.S */ /* ARM 中断向量表 */ .globl _start /* U-Boot 入口 (由 arch/arm/cpu/u-boot.lds 链接脚本 指定) */ .section ".vectors", "ax" _start: #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG .word CONFIG_SYS_DV_NOR_BOOT_CFG #endif b reset ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq ... /* arch/arm/cpu/armv7/start.S */ reset: /* CPU 复位中断入口 */ ... bl _main /* arch/arm/lib/crt0.S */ ENTRY(_main) ... mov r0, #0 bl board_init_f ... ldr pc, =board_init_r ... ENDPROC(_main)
前面一部分,是 U-Boot 启动汇编部分,做了一些基础的初始化工作,如 C 运行时环境构建
等工作。下面从 board_init_r()
开始,分析 以太网驱动 初始化过程。首先是 网络接口(RMII, RGMII等)
和 MDIO 总线
的 PIN 脚配置
:
/* 以 AM335x 的 U-Boot 为例,假定配置的是 RGMII 千兆网口 */ board_init_f() initcall_run_list(init_sequence_f) board_early_init_f() set_mux_conf_regs() /* board/xxx/xxx/mux.c */ static struct module_pin_mux rgmii1_pin_mux[] = {
{
OFFSET(mii1_crs), MODE(7)}, /* RESET */ //{OFFSET(rmii1_refclk), MODE(0) | RXACTIVE}, /* RGMII1_REFCLK */ /* * RGMII PIN 配置 (12 PINs): * RGMII1_TCTL, RGMII1_RCTL, RGMII1_TCLK, RGMII1_RCLK, RGMII1_TD0~3, RGMII1_RD0~3 */ {
OFFSET(mii1_txen), MODE(2)}, /* RGMII1_TCTL */ {
OFFSET(mii1_rxdv), MODE(2) | RXACTIVE}, /* RGMII1_RCTL */ {
OFFSET(mii1_txd3), MODE(2)}, /* RGMII1_TD3 */ {
OFFSET(mii1_txd2), MODE(2)}, /* RGMII1_TD2 */ {
OFFSET(mii1_txd1), MODE(2)}, /* RGMII1_TD1 */ {
OFFSET(mii1_txd0), MODE(2)}, /* RGMII1_TD0 */ {
OFFSET(mii1_txclk), MODE(2)}, /* RGMII1_TCLK */ {
OFFSET(mii1_rxclk), MODE(2) | RXACTIVE}, /* RGMII1_RCLK */ {
OFFSET(mii1_rxd3), MODE(2) | RXACTIVE}, /* RGMII1_RD3 */ {
OFFSET(mii1_rxd2), MODE(2) | RXACTIVE}, /* RGMII1_RD2 */ {
OFFSET(mii1_rxd1), MODE(2) | RXACTIVE}, /* RGMII1_RD1 */ {
OFFSET(mii1_rxd0), MODE(2) | RXACTIVE}, /* RGMII1_RD0 */ /* MDIO 总线 PIN 配置: MDIO DATA,CLK */ {
OFFSET(mdio_data), MODE(0) | RXACTIVE | PULLUP_EN},/* MDIO_DATA */ {
OFFSET(mdio_clk), MODE(0) | PULLUP_EN}, /* MDIO_CLK */ {
-1}, }; void set_mux_conf_regs(void) {
... configure_module_pin_mux(rgmii1_pin_mux); ... }
接着是 PHY
和 以太网 MAC 芯片
的初始化:
/* common/board_r.c */ init_fnc_t init_sequence_r[] = {
... #ifdef CONFIG_CMD_NET ... initr_net, #endif ... }; board_init_r() ... if (initcall_run_list(init_sequence_r)) hang(); ... /* lib/initcall.c */ initcall_run_list() ... for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
... ret = (*init_fnc_ptr)(); /* initr_net() */ ... } /* common/board_r.c */ initr_net() puts("Net: "); eth_initialize(); /* 以 legacy 驱动模式 为例 */ ... /* net/eth_legacy.c */ eth_initialize() ... eth_devices = NULL; eth_current = NULL; /* 1. MII 和 PHY 芯片初始化 */ eth_common_init(); ... #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB) miiphy_init(); INIT_LIST_HEAD(&mii_devs); current_mii = NULL; #endif #ifdef CONFIG_PHYLIB phy_init(); /* 注册 PHY 芯片驱动 */ #endif /* 2. 每个板型特定的 以太网 MAC 芯片 相关的初始化 */ if (board_eth_init != __def_eth_init) {
if (board_eth_init(gd->bd) < 0) /* 这里以 TI 的 AM335X 板型为例加以说明 */ printf("Board Net Initialization Failed\n"); } else if (cpu_eth_init != __def_eth_init) {
/* CPU 架构类型 特定的 以太网初始化 */ ... } else {
printf("Net Initialization Skipped\n"); } if (!eth_devices) {
/* 没有找到 或 没有成功初始化 以太网芯片 */ puts("No ethernet found.\n"); ... } else {
/* 成功初始化 以太网芯片 */ ... } ...
下面分别看一下 PHY 芯片
和 以太网 MAC 芯片
的 初始化过程。先看 PHY 芯片驱动
的注册过程:
/* drivers/net/phy/phy.c */ phy_init(); ... #ifdef CONFIG_PHY_ATHEROS phy_atheros_init(); #endif ... /* drivers/net/phy/aetheros.c */ static struct phy_driver AR8021_driver = {
.name = "AR8021", .uid = 0x4dd040, .mask = 0x4ffff0, .features = PHY_GBIT_FEATURES, .config = ar8021_config, .startup = genphy_startup, .shutdown = genphy_shutdown, }; static struct phy_driver AR8031_driver = {
.name = "AR8031/AR8033", .uid = 0x4dd074, .mask = 0xffffffef, .features = PHY_GBIT_FEATURES, .config = ar8031_config, .startup = genphy_startup, .shutdown = genphy_shutdown, }; static struct phy_driver AR8035_driver = {
.name = "AR8035", .uid = 0x4dd072, .mask = 0xffffffef, .features = PHY_GBIT_FEATURES, .config = ar8035_config, .startup = genphy_startup, .shutdown = genphy_shutdown, }; /* 向系统注册了3个类型 PHY 芯片的驱动 */ phy_atheros_init() phy_register(&AR8021_driver); phy_register(&AR8031_driver); phy_register(&AR8035_driver); ... /* drivers/net/phy/phy.c */ phy_register() INIT_LIST_HEAD(&drv->list); list_add_tail(&drv->list, &phy_drivers); /* 添加到 PHY 驱动列表 */
再看特定板型的 以太网芯片
初始化过程:
/* board/myirtech/myd_c335x/myd_c335x.c */ board_eth_init() ... /* 设置为 RMII 接口模式 & 使能时钟 */ writel(RMII_MODE_ENABLE | RMII_CHIPCKL_ENABLE, &cdev->miisel); /* 复位 PHY 芯片 */ board_phy_init(); /* 注册 以太网设备 到系统 */ rv = cpsw_register(&cpsw_data); struct cpsw_priv *priv; /* 以太网设备 对象 私有数据 */ struct eth_device *dev; /* 以太网设备 对象 */ /* 创建 以太网设备 对象 */ dev = calloc(sizeof(*dev), 1); ... /* 创建 以太网设备 对象 私有数据 */ priv = calloc(sizeof(*priv), 1); priv->dev = dev; priv->data = *data; /* 设置 以太网设备 对象 名称 和 接口 */ strcpy(dev->name, "cpsw"); dev->iobase = 0; dev->init = cpsw_init; dev->halt = cpsw_halt; dev->send = cpsw_send; dev->recv = cpsw_recv; dev->priv = priv; /* 注册 以太网设备 到系统 */ eth_register(dev); ... if (!eth_devices) {
eth_devices = dev; eth_current = dev; ... } else {
... } dev->state = ETH_STATE_INIT; dev->next = eth_devices; dev->index = index++; ... /* 注册 MDIO 总线, 连接 以太网芯片 和 PHY 芯片 */ ret = _cpsw_register(priv); ... /* 以太网芯片 上连接的 slave (PHY 芯片) */ priv->slaves = malloc(sizeof(struct cpsw_slave) * data->slaves); ... /* 注册 MDIO 总线 到系统 */ cpsw_mdio_init(priv->dev->name, data->mdio_base, data->mdio_div); struct mii_dev *bus = mdio_alloc(); ... bus->read = cpsw_mdio_read; bus->write = cpsw_mdio_write; strcpy(bus->name, name); mdio_register(bus); ... /* add it to the list */ list_add_tail(&bus->link, &mii_devs); if (!current_mii) current_mii = bus; ... ... /* 以太网芯片 上连接的 slave (PHY 芯片) 的 初始化 和 连接 */ for_each_slave(slave, priv) cpsw_phy_init(priv, slave); ... /* 关联 以太网芯片 和 PHY 芯片 */ phydev = phy_connect(priv->bus, slave->data->phy_addr, priv->dev, slave->data->phy_if); ... priv->phydev = phydev; /* 关联 PHY 和 以太网芯片 MAC */ /* 初始配置 PHY 芯片 */ phy_config(phydev); ... ...
来仔细看下 以太网芯片
和 PHY 芯片
的连接过程 phy_connect()
。期间扫描 MDIO
总线上的 PHY
设备,读取 PHY
芯片 ID
,为扫描到的 PHY
创建设备对象,加载匹配的 PHY
驱动,并调用 PHY
驱动 .startup
回调接口进行 PHY 的初始化。
phy_connect() /* drivers/net/phy/phy.c */ struct phy_device *phydev; phydev = phy_find_by_mask(bus, 1 << addr, interface); /* 创建 phy 设备对象(struct phy_device) */ if (phydev) phy_connect_dev(phydev, dev); /* 关联 以太网芯片设备 和 PHY 设备 */ else printf("Could not get PHY for %s: addr %d\n", bus->name, addr); return phydev; phy_find_by_mask() /* Reset the bus */ if (bus->reset) {
bus->reset(bus); /* Wait 15ms to make sure the PHY has come out of hard reset */ udelay(15000); } return get_phy_device_by_mask(bus, phy_mask, interface); get_phy_device_by_mask() int i; struct phy_device *phydev; ... /* Try Standard (ie Clause 22) access */ /* Otherwise we have to try Clause 45 */ for (i = 0; i < 5; i++) {
phydev = create_phy_by_mask(bus, phy_mask, i ? i : MDIO_DEVAD_NONE, interface); ... if (phydev) return phydev; } ... create_phy_by_mask() u32 phy_id = 0xffffffff; while (phy_mask) {
int addr = ffs(phy_mask) - 1; int r = get_phy_id(bus, addr, devad, &phy_id); /* 读取 PHY 芯片 ID, 用于匹配 PHY 驱动 */ /* If the PHY ID is mostly f's, we didn't find anything */ if (r == 0 && (phy_id & 0x1fffffff) != 0x1fffffff) return phy_device_create(bus, addr, phy_id, interface); phy_mask &= ~(1 << addr); } return NULL; phy_device_create() struct phy_device *dev; dev = malloc(sizeof(*dev)); ... memset(dev, 0, sizeof(*dev)); dev->duplex = -1; dev->link = 0; dev->interface = interface; dev->autoneg = AUTONEG_ENABLE; dev->addr = addr; dev->phy_id = phy_id; dev->bus = bus; dev->drv = get_phy_driver(dev, interface); phy_probe(dev); /* PHY 驱动 probe */ phydev->advertising = phydev->supported = phydev->drv->features; phydev->mmds = phydev->drv->mmds; if (phydev->drv->probe) err = phydev->drv->probe(phydev); bus->phymap[addr] = dev; return dev; get_phy_driver() ... /* 找到匹配的 PHY 驱动 */ list_for_each(entry, &phy_drivers) {
/* 这里正是前面 eth_initialize() -> phy_init() 注册的 PHY 驱动 */ drv = list_entry(entry, struct phy_driver, list); if ((drv->uid & drv->mask) == (phy_id & drv->mask)) return drv; } ...
phy_connect()
触发 PHY 驱动的 .startup
回调进行 PHY 的初始化,phy_config() 触发 PHY 驱动的 .config
回调对 PHY 进行进一步配置:
phy_config(phydev) board_phy_config(phydev); if (phydev->drv->config) {
return phydev->drv->config(phydev); }
到此,以太网芯片
和 PHY 芯片
初始配置工作已经完成,可以用来收发数据了。
3. 以太网设备的数据收发过程
以 U-Boot 的 ping
命令为例,来说明 以太网设备的数据收发过程。
/* cmd/net.c */ do_ping() ... net_ping_ip = string_to_ip(argv[1]); /* ping 的 IP 地址 */ ... if (net_loop(PING) < 0) {
/* ping 失败了 */ printf("ping failed; host %s is not alive\n", argv[1]); return CMD_RET_FAILURE; } printf("host %s is alive\n", argv[1]); ... /* net/net.c */ net_loop() ... /* 网络系统初始化 */ net_init(); static int first_call = 1; if (first_call) {
net_tx_packet = &net_pkt_buf[0] + (PKTALIGN - 1); net_tx_packet -= (ulong)net_tx_packet % PKTALIGN; for (i = 0; i < PKTBUFSRX; i++) {
net_rx_packets[i] = net_tx_packet + (i + 1) * PKTSIZE_ALIGN; } arp_init(); arp_wait_packet_ethaddr = NULL; net_arp_wait_packet_ip.s_addr = 0; net_arp_wait_reply_ip.s_addr = 0; arp_wait_tx_packet_size = 0; arp_tx_packet = &arp_tx_packet_buf[0] + (PKTALIGN - 1); arp_tx_packet -= (ulong)arp_tx_packet % PKTALIGN; net_clear_handlers(); net_set_udp_handler(NULL); net_set_arp_handler(NULL); net_set_timeout_handler(0, NULL); /* Only need to setup buffer pointers once. */ first_call = 0; } /* 读取 以太网 MAC 到 @net_ethaddr */ net_init_loop(); if (eth_get_dev()) // @eth_current memcpy(net_ethaddr, eth_get_ethaddr(), 6); ... ... /* * eth_is_on_demand_init() 返回非0值, 表示 以太网芯片 * 在需要真正进行通信的时候, 才进行具体芯片硬件相关的 * 初始化, 也就是推迟芯片硬件相关的初始化工作. */ if (eth_is_on_demand_init() || protocol != NETCONS) {
eth_halt(); eth_set_current(); ret = eth_init(); /* 以太网芯片初始化 */ ... eth_current->init(eth_current, gd->bd) = cpsw_init() _cpsw_init(priv, dev->enetaddr); /* 各种 以太网芯片的 寄存器配置 */ ... /* 连接的各个 PHY 相关初始化 */ for_active_slave(slave, priv) cpsw_slave_init(slave, priv); /* 连接的各个 PHY link 状态相关初始化:启动 PHY 等工作 */ cpsw_update_link(priv); for_active_slave(slave, priv) cpsw_slave_update_link(slave, priv, &link); ... phy_startup(phy); /* 启动 PHY */ /* 调用 PHY 驱动的 .startup 回调 */ if (phydev->drv->startup) return phydev->drv->startup(phydev); *link = phy->link; /* 接口模式: RGMII, RMII, MII */ if (*link) {
/* link up */ mac_control = priv->data.mac_control; if (phy->speed == 1000) mac_control |= GIGABITEN; if (phy->duplex == DUPLEX_FULL) mac_control |= FULLDUPLEXEN; if (phy->speed == 100) mac_control |= MIIEN; } ... /* * 我们熟悉的日志来了,如: * link up on port 0, speed 100, full duplex */ if (mac_control) {
printf("link up on port %d, speed %d, %s duplex\n", slave->slave_num, phy->speed, (phy->duplex == DUPLEX_FULL) ? "full" : "half"); } else {
printf("link down on port %d\n", slave->slave_num); } ... ... ... ... } ... switch (net_check_prereq(protocol)) {
/* 做一些合法性检查 */ ... case 0: /* 检查结果 ok, 执行协议相关的 初始化(如 ICMP ping) */ net_dev_exists = 1; net_boot_file_size = 0; switch (protocol) {
... #if defined(CONFIG_CMD_PING) case PING: ping_start(); printf("Using %s device\n", eth_get_name()); /* 熟悉吧: Using cpsw device */ net_set_timeout_handler(10000UL, ping_timeout_handler); /* 设置 ping 超时处理接口 */ time_handler = f; time_start = get_timer(0); time_delta = iv * CONFIG_SYS_HZ / 1000; ping_send(); /* 构建 ICMP 包 + 发送 ARP 请求 */ uchar *pkt; int eth_hdr_size; ... net_arp_wait_packet_ip = net_ping_ip; eth_hdr_size = net_set_ether(net_tx_packet, net_null_ethaddr, PROT_IP); pkt = (uchar *)net_tx_packet + eth_hdr_size; set_icmp_header(pkt, net_ping_ip); /* 构建 ICMP 包头部 */ /* size of the waiting packet */ arp_wait_tx_packet_size = eth_hdr_size + IP_ICMP_HDR_SIZE; /* and do the ARP request */ arp_wait_try = 1; arp_wait_timer_start = get_timer(0); arp_request(); /* 发送 ARP 请求把对方的 MAC 地址要过来 */ ... arp_raw_request(net_ip, net_null_ethaddr, net_arp_wait_reply_ip); uchar *pkt; struct arp_hdr *arp; int eth_hdr_size; ... pkt = arp_tx_packet; eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_ARP); pkt += eth_hdr_size; arp = (struct arp_hdr *)pkt; /* 构建 ARP 请求头部 */ arp->ar_hrd = htons(ARP_ETHER); arp->ar_pro = htons(PROT_IP); arp->ar_hln = ARP_HLEN; arp->ar_pln = ARP_PLEN; arp->ar_op = htons(ARPOP_REQUEST); /* 构建 ARP 请求 数据报 内容 */ memcpy(&arp->ar_sha, net_ethaddr, ARP_HLEN); /* source ET addr */ net_write_ip(&arp->ar_spa, source_ip); /* source IP addr */ memcpy(&arp->ar_tha, target_ethaddr, ARP_HLEN); /* target ET addr */ net_write_ip(&arp->ar_tpa, target_ip); /* target IP addr */ /* 通过 以太网芯片 将 ARP 请求包发送出去 */ net_send_packet(arp_tx_packet, eth_hdr_size + ARP_HDR_SIZE); eth_send(pkt, len); eth_current->send(eth_current, packet, length); cpsw_send() return 1; /* waiting */ break; #endif ... } ... } ... /* 网络包 收发循环 */ for (;;) {
... if (arp_timeout_check() > 0) /* ARP 请求 响应 超时检测 */ time_start = get_timer(0); /* * 收取网络包。 * 如发 ping 命令时, * 第一次收取 ARP 请求的响应数据; * 第二次收取 ICMP 包的 echo 包。 */ eth_rx(); eth_current->recv(eth_current); cpsw_recv() /* 以太网芯片的接收数据 */ ... len = _cpsw_recv(priv, &pkt); /* 从以太网芯片读取接收的数据 */ /* 网络协议栈 处理 进入的数据报 */ if (len > 0) {
net_process_received_packet(pkt, len); /* net/net.c */ ... net_rx_packet = in_packet; /* 接收的数据缓冲 */ net_rx_packet_len = len; et = (struct ethernet_hdr *)in_packet; ... eth_proto = ntohs(et->et_protlen); /* 提取 数据报 的 协议类型 */ ... switch (eth_proto) {
case PROT_ARP: /* 处理 ARP 请求 的 回应数据报 */ arp_receive(et, ip, len); struct arp_hdr *arp; struct in_addr reply_ip_addr; ... arp = (struct arp_hdr *)ip; /* ARP 请求响应头部 */ ... switch (ntohs(arp->ar_op)) {
... case ARPOP_REPLY: /* arp reply */ ... reply_ip_addr = net_read_ip(&arp->ar_spa); /* ARP 请求的目标 IP (这里是 ping 的 IP) */ /* matched waiting packet's address */ if (reply_ip_addr.s_addr == net_arp_wait_reply_ip.s_addr) {
/* 匹配目标地址 */ /* set the mac address in the waiting packet's header and transmit it */ /* 设置 ping 目标 IP 的 MAC 地址 */ memcpy(((struct ethernet_hdr *)net_tx_packet)->et_dest, &arp->ar_sha, ARP_HLEN); /* 好了,现在有了目标 IP 和 其 MAC,可以向其发送 ICMP 包了 */ net_send_packet(net_tx_packet, arp_wait_tx_packet_size); /* no arp request pending now */ net_arp_wait_packet_ip.s_addr = 0; arp_wait_tx_packet_size = 0; arp_wait_packet_ethaddr = NULL; } return; ... } break; ... case PROT_IP: ... if (ip->ip_p == IPPROTO_ICMP) {
/* 处理 ICMP 包的 echo */ receive_icmp(ip, len, src_ip, et); ... switch (icmph->type) {
... default: #if defined(CONFIG_CMD_PING) ping_receive(et, ip, len); ... switch (icmph->type) {
case ICMP_ECHO_REPLY: src_ip = net_read_ip((void *)&ip->ip_src); if (src_ip.s_addr == net_ping_ip.s_addr) /* 好的,成功收到对端对 ICMP 请求的 echo */ net_set_state(NETLOOP_SUCCESS); return; ... } #endif } return; } else if (ip->ip_p != IPPROTO_UDP) {
/* Only UDP packets */ ... } ... ... } ... } return len; ... /* 协议包超时处理(如 ICMP 回应超时) */ if (time_handler && ((get_timer(0) - time_start) > time_delta)) {
thand_f *x; ... x = time_handler; time_handler = (thand_f *)0; (*x)(); /* ping_timeout_handler() */ } /* 本次失败之后,可能再来一次之前(是否重来,取决于配置),重新进行以太网初始化 */ if (net_state == NETLOOP_FAIL) ret = net_start_again(); /* 如果配置了重试(如 "netretry"="yes"),返回 NETLOOP_RESTART */ switch (net_state) {
case NETLOOP_RESTART: /* 重试 */ net_restarted = 1; goto restart; case NETLOOP_SUCCESS: /* 成功,做网络设置清理工作,然后退出循环 */ net_cleanup_loop(); ... goto done; case NETLOOP_FAIL: /* 失败,并且没有配置重试,做网络设置清理工作,然后退出循环 */ net_cleanup_loop(); ... goto done; } }
arp_timeout_check() ulong t; ... t = get_timer(0); /* check for arp timeout */ if ((t - arp_wait_timer_start) > ARP_TIMEOUT) {
arp_wait_try++; if (arp_wait_try >= ARP_TIMEOUT_COUNT) {
/* 重发了 ARP_TIMEOUT_COUNT 次,仍然没收到回应 */ puts("\nARP Retry count exceeded; starting again\n"); arp_wait_try = 0; net_set_state(NETLOOP_FAIL); } else {
arp_wait_timer_start = t; arp_request(); /* 再次发送 ARP 请求 */ } }
这时的错误报告信息内容如下:
ARP Retry count exceeded; starting again
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/115711.html