以太网驱动程序指南
Das U-Boot 中的网络堆栈设计允许在运行时轻松添加和控制多个网络设备。本指南适用于希望回顾网络驱动堆栈并实现自己以太网设备驱动程序的人员。在这里,我们将描述一个新的伪“APE”驱动程序。
大多数现有驱动程序已经(并且新的网络驱动程序必须)使用 U-Boot 核心驱动模型。有关这方面的通用信息可以在 doc/driver-model/design.rst
中找到,因此本文档将重点介绍网络特定的代码部分。一些驱动程序仍在使用旧的以太网接口,旧接口和新接口之间的差异以及移植提示将在文末处理。
驱动框架
遵循驱动模型的网络驱动程序必须使用 U-Boot 驱动程序结构中的 UCLASS_ETH
.id 字段来声明自己:
U_BOOT_DRIVER(eth_ape) = { .name = "eth_ape", .id = UCLASS_ETH, .of_match = eth_ape_ids, .of_to_plat = eth_ape_of_to_plat, .probe = eth_ape_probe, .ops = ð_ape_ops, .priv_auto = sizeof(struct eth_ape_priv), .plat_auto = sizeof(struct eth_ape_pdata), .flags = DM_FLAG_ALLOC_PRIV_DMA, };
struct eth_ape_priv
包含运行时的每个实例数据,如缓冲区、指向当前描述符的指针、当前速度设置、指向 PHY 相关数据的指针(如 struct mii_dev
)等。在 .priv_auto
中声明其大小将使驱动框架在适当的时间分配它。可以通过 dev_get_priv(dev)
调用来检索。
struct eth_ape_pdata
包含静态平台数据,如 MMIO 基地址、硬件变体、MAC 地址。struct eth_pdata
作为该结构的第一个成员有助于避免重复代码。如果您除了标准成员之外不需要更多平台数据,只需使用 sizeof(struct eth_pdata)
作为 plat_auto
。
PCI 设备添加了一个指向受支持的供应商/设备 ID 对的行:
cstatic struct pci_device_id supported[] = { { PCI_DEVICE(PCI_VENDOR_ID_APE, 0x4223) }, {} }; U_BOOT_PCI_DEVICE(eth_ape, supported);
也可以声明对整个 PCI 设备类别的支持:
c{ PCI_DEVICE_CLASS(PCI_CLASS_SYSTEM_SDHCI << 8, 0xffff00) },
设备探测和实例化将由驱动模型框架处理,因此请遵循相关指南。probe()
函数将初始化硬件的特定平台部分,如时钟、复位、GPIO、MDIO 总线。同时,它还会处理任何特殊的 PHY 设置(电源轨、内部 PHY 的启用位等)。
驱动方法
实际工作将在驱动程序提供的驱动方法函数中完成,通过定义 struct eth_ops
的成员:
struct eth_ops { int (*start)(struct udevice *dev); int (*send)(struct udevice *dev, void *packet, int length); int (*recv)(struct udevice *dev, int flags, uchar **packetp); int (*free_pkt)(struct udevice *dev, uchar *packet, int length); void (*stop)(struct udevice *dev); int (*mcast)(struct udevice *dev, const u8 *enetaddr, int join); int (*write_hwaddr)(struct udevice *dev); int (*read_rom_hwaddr)(struct udevice *dev); };
最新版本的 struct
及更多信息可以在 include/net.h
中找到。
只有 start
、stop
、send
和 recv
是必需的,其余的为可选的,通常由通用代码处理或在未提供时被忽略。
start
函数初始化硬件并使其准备好进行发送/接收操作。你通常会在这里做一些操作,如重置 MAC 和/或 PHY,并等待链路自动协商。你还应该利用这个机会用 enetaddr
成员(这是你自己 plat
结构的第一个成员)来编程设备的 MAC 地址。这使 U-Boot 的其余部分可以动态更改 MAC 地址并使新设置生效。
send
函数执行你想的事情——传输指定大小的包(以字节为单位)。包缓冲区可以(也将会!)被重用于后续的 send()
调用,因此在 send()
函数返回时必须不再使用。实现这一点的最简单方法是等待传输完成。或者,如果硬件支持,只等待缓冲区被消耗(由某个 DMA 引擎)也可能是一种选择。另一种消耗缓冲区的方式是将数据复制到要发送的包中,然后将复制的包排队(例如交给 DMA 引擎),然后立即返回。在任何情况下,你都应该使状态保持在可以多次连续调用 send
函数的状态。
recv
函数轮询新包的可用性。如果没有可用包,它必须返回 -EAGAIN
。如果收到一个包,确保它对 CPU 可访问(如有必要,失效缓存),然后将其地址写入 packetp
指针,并返回长度。如果发生错误(接收错误、包过短或过长),如果需要包被正常清理,则返回 0,否则返回负错误代码(不需要清理或已经完成清理)。U-Boot 网络堆栈将处理该包。
如果定义了 free_pkt
,U-Boot 会在处理完接收到的包后调用它,以便释放或回收包缓冲区。通常你会将其交还给硬件以获取另一个包。free_pkt()
会在 recv()
之后被调用,对于同一个包,因此你不必从包指针推断要释放的缓冲区,而可以依赖于这是 recv()
处理的最后一个包。通用代码已经在 .bss
(net_rx_packets
)中为你设置了包缓冲区,因此通常不需要分配自己的缓冲区。然而,这并不意味着你必须使用 net_rx_packets
数组;你可以自由使用任何你希望的缓冲区。
stop
函数应该关闭/禁用硬件并将其放回重置状态。它可以在任何时间(在相关 start()
函数调用之前)调用,因此请确保它能处理这种情况。
(可选)write_hwaddr
函数应将存储在 pdata->enetaddr
中的 MAC 地址编程到以太网控制器中。
所以此时的调用图可能如下:
scss(某些网络操作(ping / tftp / whatever...)) eth_init() ops->start() eth_send() ops->send() eth_rx() ops->recv() (处理包) 如果 (ops->free_pkt) ops->free_pkt() eth_halt() ops->stop()
CONFIG_PHYLIB / CONFIG_CMD_MII
如果你的设备支持在 MII 总线上使用任意值(几乎所有设备都支持),你应该添加对 mii
命令的支持。这样做相当简单,并且使得在运行时调试 MII 问题变得容易得多。
在驱动程序的 probe()
函数中,添加对 mdio_alloc()
和 mdio_register()
的调用,如下所示:
bus = mdio_alloc(); if (!bus) { ... return -ENOMEM; } bus->read = ape_mii_read; bus->write = ape_mii_write; mdio_register(bus);
然后定义 mii_read
和 mii_write
函数(如果你还没有定义的话)。它们的语法很简单:
int mii_read(struct mii_dev *bus, int addr, int devad, int reg); int mii_write(struct mii_dev *bus, int addr, int devad, int reg, u16 val);
read
函数应该从地址为 addr
的 PHY 读取寄存器 reg
,并将结果返回给调用者。write
函数的实现应该逻辑上跟随 read
。
旧版网络驱动程序
!!! 警告 !!!
以下部分描述了旧的做法。任何新的以太网驱动程序都不应以这种方式实现。所有新的驱动程序都应按照上述 U-Boot 核心驱动模型编写。
实际的回调函数非常类似,区别在于:
start()
被称为 init()
stop()
被称为 halt()
recv()
函数必须循环直到接收到所有数据包,对于每个数据包,它必须调用 net_process_received_packet()
函数,将指针和长度传递给它。之后,它应该释放包,然后检查新数据。
要将旧驱动程序移植到新
标签:指南,struct,int,dev,网卡,驱动,eth,ape,驱动程序 From: https://www.cnblogs.com/aldary/p/18351047