网络框架
网络框架 | 程序 | 功能 |
---|---|---|
Socket | net/ipv4/socket.c | 提供Socket通信API |
传输层(TCP/UDP) | net/ipv4/tcp.c udp.c | 提供TCP/UDP协议 |
网络层(IP) | net/ipv4/ip_x.c | 网络层协议,增加IP包头 |
链路层(Ethernet) | net/ethernet/eth.c | 链路层协议,增加DataLinkHeader |
驱动 | dm9000.c | 定义设备类;设备树匹配检测;初始化空间及注册;实现设备文件操作; |
设备树移植
网卡芯片需要注意的引脚:
引脚 | 网卡电路图对应丝印 | 板子丝印 |
---|---|---|
cs | BUF_Xm0cs1 | Xm0cs1 |
INT | DM9000_IRQ | GPX0_6 |
参考文档:
kernel-4.9/Documentation/devicetree/bindings/net/davicom-dm9000.txt
ethernet@18000000 { compatible = "davicom,dm9000"; reg = <0x18000000 0x2 0x18000004 0x2>; interrupt-parent = <&gpn>; interrupts = <7 4>; local-mac-address = [00 00 de ad be ef]; davicom,no-eeprom; reset-gpios = <&gpf 12 GPIO_ACTIVE_LOW>; vcc-supply = <ð0_power>; }
-
reg = <0x18000000 0x2 0x18000004 0x2>;
-
在SROM控制器章节,将网卡看作是cpu内存的扩展,cs线找到对应cpu的地址
-
-
interrupt-parent = <&gpn>;
-
找到中断对应的GPIO,将该GPIO属性填入
-
-
interrupts = <7 4>;
-
7: 中断连接GPIO管脚号
-
4: 触发方式
-
驱动源码分析
一、网卡驱动程序框架
在一个网络驱动程序中,一般都提供了一个platform_driver结构变量。
platform_driver结构包括了网卡驱动的相关操作函数,通过platform_driver_register()函数注册到内核设备驱动列表,提供以下函数:、
probe:加载网卡驱动的时候执行,主要用于初始化网卡硬件接口,设置网络接口函数;
remove:卸载网卡驱动的时候执行该函数,用于从系统中注销网络接口函数;
static struct platform_driver dm9000_driver = { .driver = { .name = "dm9000", .pm = &dm9000_drv_pm_ops, .of_match_table = of_match_ptr(dm9000_of_matches), }, .probe = dm9000_probe, .remove = dm9000_drv_remove, };
二、定义设备类
struct board_info { void __iomem *io_addr; //dm9000的寄存器 IO地址 void __iomem *io_data; //dm9000的数据 IO地址 u16 irq; //中断号 u16 tx_pkt_cnt; //网卡SRAM中,待网络包的数目 u16 queue_pkt_len; //SKB发送队列中的数据包个数 u16 queue_start_addr; //SKB发送队列的其实地址 u8 phy_addr; //物理网卡地址 struct device *dev; //统一设备模型 //设备树里的资源 struct resource *addr_res; struct resource *data_res; struct mutex addr_lock; //互斥锁 spinlock_t lock; //自旋锁 struct delayed_work phy_poll; //延迟工作队列 struct net_device *ndev; //继承网络设备,结构体过于冗杂 void (*inblk)(void __iomem *port, void *data, int length); //从dm9000读一块数据 void (*outblk)(void __iomem *port, void *data, int length); //向dm9000写一块数据 };
在 board_info结构中,
-
io_addr和 io_data成员变量存放了控制寄存器和数据寄存器地址;
-
tx_pkt_cnt记录待发送数据包个数;
-
queue_pkt_len记录了发送队列中数据包个数;
-
queue_start_addr记录了数据包发送队列的起始地址;
-
phy_addr是网卡的物理地址;
三、驱动初始化 dm9000_probe
1. 获取设备资源
根据设备树,获取设备资源
power = devm_regulator_get(dev, "vcc"); //获取设备树中vcc(电源值) ret = regulator_enable(power); //电源使能 /* of_get_named_gpio_flags函数,获取设备树节点中reset-gpios(复位管脚)对应的GPIO管脚号 */ reset_gpios = of_get_named_gpio_flags(dev->of_node, "reset-gpios", 0, &flags); /* 判断该gpio是否被占用 */ if (gpio_is_valid(reset_gpios)) { /* * 申请gpio, 该函数申请GPIO后 * 可以使用其他函数(gpio_direction_input、gpio_direction_output)来配置 GPIO 引脚的工作模式 * 可以使用其他函数(gpio_set_value、gpio_get_value)来设置或获取 GPIO 引脚的电平状态 */ ret = devm_gpio_request_one(dev, reset_gpios, flags, "dm9000_reset"); /* 设置复位值 */ gpio_set_value(reset_gpios, 1); } /* 解析设备树信息 */ pdata = dm9000_parse_dt(&pdev->dev);
2. 分配board_info空间
分配board_info结构体占有的私有资源,在程序中使用 alloc_etherdev()函数分配网卡驱动使用的私有资源
/* 分配网络空间,并初始化 */ ndev = alloc_etherdev(sizeof(struct board_info)); //分配net_device的空间
3. 初始化board_info结构体
分配资源成功后,开始初始化 board_info结构,设置结构的值为0,然后初始化 spin_lock,spin_lock称做自旋锁,是内核中用于临界资源的一种结构。
初始化自旋锁以后,程序分配网络适配器 I/O地址寄存器、数据寄存器用到的内存,并且映射到内核空间。
/* 安装网络设备(把设备对象db里的ndev指向实体) */ db = netdev_priv(ndev); /* 设备对象db赋初值 */ db->dev = &pdev->dev; db->ndev = ndev; /* 初始化对象db中的自旋锁和互斥锁 */ spin_lock_init(&db->lock); mutex_init(&db->addr_lock); /* 初始化延迟工作队列(工作队列+定时器) -> 工作队列可以周期性检测 */ INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work); /* 获取平台设备资源 */ db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //DM9000的寄存器地址 db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); //DM9000的数据地址 /* 获取中断资源 */ ndev->irq = platform_get_irq(pdev, 0); /* * 读取硬件地址需要将物理地址转换成寄存器的虚地址 * 1. 计算数据寄存器地址空间 * 2. 申请mem资源 * 3. 映射物理地址 */ /* 地址寄存器初始化 */ iosize = resource_size(db->addr_res); db->addr_req = request_mem_region(db->addr_res->start, iosize, pdev->name); db->io_addr = ioremap(db->addr_res->start, iosize); /* 数据寄存器初始化 */ iosize = resource_size(db->data_res); db->data_req = request_mem_region(db->data_res->start, iosize, pdev->name); db->io_data = ioremap(db->data_res->start, iosize); /* 设置网络设备I/O地址 */ ndev->base_addr = (unsigned long)db->io_addr; /* 该函数用于数据I/O的方法,io有8位、16位、32位,这里设置了默认的方法 */ dm9000_set_io(db, iosize);
4. 设置数据I/O回调函数
检查是否需要继承系统提供的函数。
初始化网络适配器数据结构后,需要设置网络设备用到的回调函数。
if (pdata != NULL) { /* 网卡数据总线有8位、16位、32位等等,将对应的方法绑定到db中 */ if (pdata->flags & DM9000_PLATF_8BITONLY) dm9000_set_io(db, 1); //8位 if (pdata->flags & DM9000_PLATF_16BITONLY) dm9000_set_io(db, 2); //16位 if (pdata->flags & DM9000_PLATF_32BITONLY) dm9000_set_io(db, 4); //32位 /* 输入输出方法绑定 */ if (pdata->inblk != NULL) db->inblk = pdata->inblk; if (pdata->outblk != NULL) db->outblk = pdata->outblk; if (pdata->dumpblk != NULL) db->dumpblk = pdata->dumpblk; db->flags = pdata->flags; }
5. 检测ID值
设置DM9000网络适配器芯片ID
/* 网卡复位 * 1. 写(NCR_RST | NCR_MAC_LBK)值,到网卡复位寄存器地址(DM9000_NCR) * iow(db, DM9000_NCR, NCR_RST | NCR_MAC_LBK); * 2. 读取DM9000_NCR寄存器,看是否复位成功 * if (ior(db, DM9000_NCR) & 1) * dev_err(db->dev, "dm9000 did not respond to first reset\n"); */ dm9000_reset(db); /* 多次读取设备的ID值,确认是DM9000设备 */ /* 如果网卡设备发生故障,先看是否读ID值错误read wrong id 0x%08x */ for (i = 0; i < 8; i++) { id_val = ior(db, DM9000_VIDL); id_val |= (u32)ior(db, DM9000_VIDH) << 8; id_val |= (u32)ior(db, DM9000_PIDL) << 16; id_val |= (u32)ior(db, DM9000_PIDH) << 24; if (id_val == DM9000_ID) break; dev_err(db->dev, "read wrong id 0x%08x\n", id_val); } /* 检查芯片 ID是否正确 */ if (id_val != DM9000_ID) { dev_err(db->dev, "wrong id: 0x%08x\n", id_val); ret = -ENODEV; goto out; }
6. 注册勾连接口函数
设置网卡芯片用到的接口函数
//设置网卡芯片的接口函数 ndev->netdev_ops = &dm9000_netdev_ops; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ndev->ethtool_ops = &dm9000_ethtool_ops;
7. 判断EEPROM值
设置回调函数后,设置MII接口,MII接口不是所有的处理器都支持,设置的目的是供支持MII接口的处理器使用。
db->msg_enable = NETIF_MSG_LINK; db->mii.phy_id_mask = 0x1f; db->mii.reg_num_mask = 0x1f; db->mii.force_media = 0; db->mii.full_duplex = 0; db->mii.dev = ndev; db->mii.mdio_read = dm9000_phy_read; // MII方式读函数 db->mii.mdio_write = dm9000_phy_write; // MII方式写函数 /* 网卡内部有一个eeprom,可以存储网卡的一些信息参数 */ mac_src = "eeprom"; /* 从连接的网卡设备EEPROM中读取节点地址 */ for (i = 0; i < 6; i += 2) dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i); //验证EEPROM值是否正确 if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) { mac_src = "platform data"; memcpy(ndev->dev_addr, pdata->dev_addr, ETH_ALEN); } /* 第一次判断EEPROM值 */ if (!is_valid_ether_addr(ndev->dev_addr)) { /* 如果是不合法地址,则从DM9000内部寄存器中重新读取 MAC地址 */ mac_src = "chip"; for (i = 0; i < 6; i++) ndev->dev_addr[i] = ior(db, i+DM9000_PAR); } /* 第二次判断EEPROM值 */ if (!is_valid_ether_addr(ndev->dev_addr)) { inv_mac_addr = true; eth_hw_addr_random(ndev); mac_src = "random";
8. 注册网络设备驱动
/* 注册网络设备驱动 */ platform_set_drvdata(pdev, ndev); /* 注册网络设备 */ ret = register_netdev(ndev); /* 成功后打印出来网卡的关键信息 */ if (ret == 0) { if (inv_mac_addr) dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please set using ip\n", ndev->name); printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n", ndev->name, dm9000_type_to_char(db->type), db->io_addr, db->io_data, ndev->irq, ndev->dev_addr, mac_src); } return 0;
四、实现文件操作
网络设备的操作函数集,供上层APP调用,比如socket、ifconfig等
static const struct net_device_ops dm9000_netdev_ops = { .ndo_open = dm9000_open, //打开网卡设备(ifconfig时激活),该函数用于网卡软启动 .ndo_stop = dm9000_stop, //停止网卡设备(ifdown命令设置网卡暂时停止) .ndo_start_xmit = dm9000_start_xmit, //发送网络包 .ndo_tx_timeout = dm9000_timeout, //超时操作 .ndo_set_rx_mode = dm9000_hash_table, .ndo_do_ioctl = dm9000_ioctl, .ndo_change_mtu = eth_change_mtu, .ndo_set_features = dm9000_set_features, .ndo_validate_addr = eth_validate_addr, .ndo_set_mac_address = eth_mac_addr, #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = dm9000_poll_controller, #endif };
1. 打开网卡设备(dm9000_open)
/* 该函数用于初始化dm9000网卡 * 1. 复位dm9000网卡 * 2. 关闭网卡内部的中断 * 3. 设置网卡设备内部属性 */ dm9000_init_dm9000(dev); /* 4. 申请中断号 */ if (request_irq(dev->irq, dm9000_interrupt, irq_flags, dev->name, dev)) return -EAGAIN; /* 5. 打开网卡内部的中断,向中断寄存器写入值 */ dm9000_unmask_interrupts(db); /* 6. 检测网卡媒介状态(Link网卡是否连接),调用netif_carrier_on载波侦听 */ mii_check_media(&db->mii, netif_msg_link(db), 1); /* 7. 启动SKB发送队列,向网卡SRAM写数据 */ netif_start_queue(dev); /* 8. 启动延迟工作队列 -> 周期性(以1为周期)调用db->phy_poll函数,该函数不断调用mii_check_media来检测网卡是否连接 */ schedule_delayed_work(&db->phy_poll, 1);
2. 关闭网卡设备(dm9000_stop)
/* 1. 取消延迟工作队列 */ cancel_delayed_work_sync(&db->phy_poll); /* 2. 停止数据包发送工作=队列 */ netif_stop_queue(ndev); /* 3. 停止载波监听 */ netif_carrier_off(ndev); /* 4. 释放中断号 */ free_irq(ndev->irq, ndev); /* 5. 关闭网卡(向网卡设备相应寄存器中写值) */ dm9000_shutdown(ndev);
3. 超时操作(dm9000_timeout)
DM9000网络驱动会使用一种定时机制或计数器来跟踪网络传输的时间。当发送或接收数据包时,会设置一个超时时间戳,然后在后续的处理中检查是否已经超过了这个时间戳,如果超过了就认为发生了超时操作。
db->in_timeout 是超时的锁状态,用来线程间互斥
/* 1. 自旋锁互斥保护 */ spin_lock_irqsave(&db->lock, flags); db->in_timeout = 1; reg_save = readb(db->io_addr); /* 2. 停止发送队列 */ netif_stop_queue(dev); /* 3. 重新初始化网卡(软重启复位网卡) */ dm9000_init_dm9000(dev); /* 4. 取消中断屏蔽 */ dm9000_unmask_interrupts(db); /* 5. 查找并返回指向该网络队列的指针 */ netif_trans_update(dev); /* 6. 唤醒发送队列 */ netif_wake_queue(dev); writeb(reg_save, db->io_addr); db->in_timeout = 0; /* 7. 关闭自旋锁互斥 */ spin_unlock_irqrestore(&db->lock, flags);
4. 发送网络包(dm9000_start_xmit)
网卡驱动程序需要向内核提供两个发送数据包的回调函数,一个用于发送数据包,一个用于数据包发送完毕后的处理。
/* 1. 发送条件检测,SRAM空间只能容纳一个包,当待发送包大于1时,返回BUSY */ if (db->tx_pkt_cnt > 1) return NETDEV_TX_BUSY; /* 2. 自旋锁保护 */ spin_lock_irqsave(&db->lock, flags); /* 3. 写初始化寄存器 */ writeb(DM9000_MWCMD, db->io_addr); /* 4. 将skb中数据块写入网卡RAM中 */ (db->outblk)(db->io_data, skb->data, skb->len); dev->stats.tx_bytes += skb->len; /* 5. 记录写入网卡SRAM中待发送数据包的数量 */ db->tx_pkt_cnt++; /* 注: * a. 网卡的SRAM中,有两个buffer区域,buffer1存放当前发送数据,buffer2存放等待发送数据包(用tx_pkt_cnt记录数据包数量) * b. 判断当前待发送数据包数量,本来应该是0,加上这个数据包是1,如果不等于1,则说明状态错误 */ if (db->tx_pkt_cnt == 1) { /* 6. 启动网卡发送数据 */ dm9000_send_packet(dev, skb->ip_summed, skb->len); } else { /* 若网卡RAM不等于,则停止发送队列,不要往网卡RAM中写数据了 */ db->queue_pkt_len = skb->len; db->queue_ip_summed = skb->ip_summed; netif_stop_queue(dev); } /* 7. 释放互斥锁 */ spin_unlock_irqrestore(&db->lock, flags); /* 8. 释放掉当前skb缓冲区,因为上面已经将该缓冲区的数据拷入到SRAM中了,并且释放掉不意 */ dev_consume_skb_any(skb);
5. 数据包发送后续处理函数(dm9000_tx_done)
数据包发送完毕后,会触发一个中断,内核会调用后续的处理函数 dm9000_tx_done()
if (tx_status & (NSR_TX2END | NSR_TX1END)) { /* 1. 数据包发送完成,要把当前数量减一 */ db->tx_pkt_cnt--; dev->stats.tx_packets++; /* 2. 判断释放发送完毕,看db的发送标志 * db->msg_enable & 0x0400 */ if (netif_msg_tx_done(db)) dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status); /* 3.如果数据包未发送完,立马启动发送 */ if (db->tx_pkt_cnt > 0) dm9000_send_packet(dev, db->queue_ip_summed, db->queue_pkt_len); /* 4. 如果RAM发送队列=0,则说明RAM缓存区空出来了,可以将下一个数据包放进来,唤醒发送队列 */ netif_wake_queue(dev); }
6. 接收网络包(dm9000_rx)
收到DM9000接收数据包中断后被内核调用。dm9000_rx()函数使用了一个自定义的dm9000_rxhdr结构,该结构与DM9000网络控制器提供的数据包接收信息对应。
struct dm9000_rxhdr rxhdr; //用于描述DM9000数据包结构体,即包头结构体 struct sk_buff *skb; u8 rxbyte; //存放网络控制器状态 u8 *rdptr; //存放读取数据包 bool GoodPacket; int RxLen; //存放接收数据包的长度 /* while (rxbyte & DM9000_PKT_RDY); * 该循环会读取DM9000的DM9000_PKT_RDY,检测是否还有准备读取的数据包,如果没有则循环结束 */ do { /* 1. 先读取第一个字节,如果不为DM9000_PKT_RDY,则说明数据包未准备好,则跳出循环 */ ior(db, DM9000_MRCMDX); /* 2. 读取网络控制器状态 */ rxbyte = readb(db->io_data); /* 3. 判断状态是否正确 */ if (rxbyte & DM9000_PKT_ERR) { dev_warn(db->dev, "status check fail: %d\n", rxbyte); iow(db, DM9000_RCR, 0x00); /* 状态错误时,停止网络控制器 */ return; } /* 4. 检测是否有数据需要读取 */ if (!(rxbyte & DM9000_PKT_RDY)) return; /* A packet ready now & Get status/length */ GoodPacket = true; /* 5. 向控制器发起读命令 */ writeb(DM9000_MRCMD, db->io_addr); /* 6.读取包头,网卡数据包的前四个字节(有效标志、接收状态、数据长度。 把他们存在结构体rxhdr中) */ (db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr)); RxLen = le16_to_cpu(rxhdr.RxLen); if (netif_msg_rx_status(db)) dev_dbg(db->dev, "RX: status %02x, length %04x\n", rxhdr.RxStatus, RxLen); /* 7. 判断数据包是否小于64字节,正常情况小于64字节会填充到64字节,小于则是损坏的包 */ if (RxLen < 0x40) { GoodPacket = false; if (netif_msg_rx_err(db)) dev_dbg(db->dev, "RX: Bad Packet (runt)\n"); } /* 8. 判断数据包是否超过1536字节,不能超过RAM大小 */ if (RxLen > DM9000_PKT_MAX) { dev_dbg(db->dev, "RST: RX Len:%x\n", RxLen); } /* 9. 检查接收状态是否出错 */ /* * RSR_FOE: 表示接收时发生了帧溢出错误。 * RSR_CE: 表示接收时发生了 CRC 错误。 * RSR_AE: 表示接收时发生了地址错误。 * RSR_PLE: 表示接收时发生了数据包长度错误。 * RSR_RWTO: 表示接收时发生了读等待超时错误。 * RSR_LCS: 表示链路状态改变。 * RSR_RF: 表示接收到了数据帧 */ if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE | RSR_PLE | RSR_RWTO | RSR_LCS | RSR_RF)) { GoodPacket = false; //FIFO 错误 if (rxhdr.RxStatus & RSR_FOE) { if (netif_msg_rx_err(db)) dev_dbg(db->dev, "fifo error\n"); dev->stats.rx_fifo_errors++; } //CRC 错误 if (rxhdr.RxStatus & RSR_CE) { if (netif_msg_rx_err(db)) dev_dbg(db->dev, "crc error\n"); dev->stats.rx_crc_errors++; } //包长度错误 if (rxhdr.RxStatus & RSR_RF) { if (netif_msg_rx_err(db)) dev_dbg(db->dev, "length error\n"); dev->stats.rx_length_errors++; } }
/* 10.如果条件都满足,将网卡的RAM中数据包,拷到SK_BUFFER中,并通知上层协议栈取走SKB */ /* 如果是一个好的数据包则分配skb内存,并且缓存足够时,将数据读入SK_BUFFER缓存中 */ if (GoodPacket && ((skb = netdev_alloc_skb(dev, RxLen + 4)) != NULL)) { skb_reserve(skb, 2); rdptr = (u8 *) skb_put(skb, RxLen - 4); /* 从RX SRAM中读取接收到的数据包 */ (db->inblk)(db->io_data, rdptr, RxLen); // 将网卡内部RAM的数据包读取到SKB中 dev->stats.rx_bytes += RxLen; // 更新接收字节数统计 /* 传递给上层协议栈 */ skb->protocol = eth_type_trans(skb, dev); //把SKB数据包丢给上层协议栈 /* 这里通知上层协议栈取走解析后的SKB数据包 */ netif_rx(skb); /* 增加接收数据包计数 */ dev->stats.rx_packets++; } else { /* 需要丢弃数据包的数据 */ (db->dumpblk)(db->io_data, RxLen); // 调用函数将数据包的数据从网卡内部RAM中丢弃 } } while (rxbyte & DM9000_PKT_RDY);
7. 中断处理函数(dm9000_interrupt)
网络设备驱动需要提供中断处理函数和定时处理函数供内核使用。
-
中断处理函数当网络控制器向CPU发出中断后,由内核中断处理函数调用。
-
定时器处理函数是由内核的一个定时器周期的调用。
-
网卡中断处理函数,有三个触发时机:
-
-
收到数据包时
-
-
-
数据包发送完
-
-
-
连接状态改变
-
/* 1. 开启自旋锁 */ spin_lock_irqsave(&db->lock, flags); /* 2. 保存当前中断寄存器的值 */ reg_save = readb(db->io_addr); /* 3. 中断屏蔽(操作DM9000的IMR寄存器,屏蔽所有中断) */ dm9000_mask_interrupts(db); /* 4. 获取dm9000的中断状态寄存器(ISR),判断是什么操作触发了中断 */ int_status = ior(db, DM9000_ISR); /* Got ISR */ iow(db, DM9000_ISR, int_status); /* Clear ISR status */ if (netif_msg_intr(db)) dev_dbg(db->dev, "interrupt status %02x\n", int_status); /* 检测是否是接收数据时的中断 */ if (int_status & ISR_PRS) dm9000_rx(dev); /* 检测是否数据包发送完成的中断 */ if (int_status & ISR_PTS) dm9000_tx_done(dev, db); /* 检测是否连接状态发生的中断 */ if (db->type != TYPE_DM9000E) { if (int_status & ISR_LNKCHNG) { /* fire a link-change request */ schedule_delayed_work(&db->phy_poll, 1); //启动网卡连接检测 } } /* 5. 解除中断屏蔽 */ dm9000_unmask_interrupts(db); /* 6. 恢复中断处理前中断寄存器的值 */ writeb(reg_save, db->io_addr); /* 7. 解除自旋锁 */ spin_unlock_irqrestore(&db->lock, flags); return IRQ_HANDLED;
### 参考文章