以太网驱动程序
以太网驱动主要包含以下部分:网络设备初始化、数据帧发送、数据帧接收、后台处理、网络接口注册等。
网络设备结构体
SylixOS 定义了 netdev_t 结构体,它位于“libsylixos/SylixOS/net/lwip/netdev/netdev.h”文件中,网络设备驱动只需通过填充 netdev_t 结构体成员并注册网络接口即可实现硬件操作函数与内核的挂接。
netdev_t 结构体内部封装了网络设备的属性和操作接口,其详细描述如下:
#include <SylixOS.h>
typedef struct netdev {
UINT32 magic_no;
char dev_name[IF_NAMESIZE];
char if_name[IF_NAMESIZE];
char *if_hostname;
UINT32 init_flags;
UINT32 chksum_flags;
UINT32 net_type;
UINT64 speed;
UINT32 mtu;
UINT8 hwaddr_len;
UINT8 hwaddr[NETIF_MAX_HWADDR_LEN];
struct netdev_funcs *drv;
void *priv;
int if_flags;
void *wireless_handlers;
void *wireless_data;
ULONG sys[254];
} netdev_t;
- magic_no:魔数,固定为 0xf7e34a81。
- dev_name:网络设备名称。
- if_name:内核网卡名称。
- if_hostname:网络设备主机名称。
- init_flags:网络设备初始化选项,详细取值如下表所示。
接口初始化选项值 | 含义 |
---|---|
NETDEV_INIT_LOAD_PARAM | 添加接口时载入网络参数 |
NETDEV_INIT_LOAD_DNS | 添加接口时载入 DNS 参数 |
NETDEV_INIT_IPV6_AUTOCFG | 添加接口时进行 IPv6 配置 |
NETDEV_INIT_AS_DEFAULT | 添加接口时进行默认配置 |
- chksum_flags:网络校验选项,其详细配置如下表所示。
网络校验配置宏 | 含义 |
---|---|
NETDEV_CHKSUM_GEN_IP | 配置 TCP/IP 协议栈生成 IP 校验和 |
NETDEV_CHKSUM_GEN_UDP | 配置 TCP/IP 协议栈生成 UDP 校验和 |
NETDEV_CHKSUM_GEN_TCP | 配置 TCP/IP 协议栈生成 TCP 校验和 |
NETDEV_CHKSUM_GEN_ICMP | 配置 TCP/IP 协议栈生成 ICMP 校验和 |
NETDEV_CHKSUM_GEN_ICMP6 | 配置 TCP/IP 协议栈生成 ICMP6 校验和 |
NETDEV_CHKSUM_CHECK_IP | 配置 TCP/IP 协议栈检查 IP 校验和 |
NETDEV_CHKSUM_CHECK_UDP | 配置 TCP/IP 协议栈检查 UDP 校验和 |
NETDEV_CHKSUM_CHECK_TCP | 配置 TCP/IP 协议栈检查 TCP 校验和 |
NETDEV_CHKSUM_CHECK_ICMP | 配置 TCP/IP 协议栈检查 ICMP 校验和 |
NETDEV_CHKSUM_CHECK_ICMP6 | 配置 TCP/IP 协议栈检查 ICMP6 校验和 |
NETDEV_CHKSUM_ENABLE_ALL | 配置 TCP/IP 协议栈生成/检查所有检验和 |
NETDEV_CHKSUM_DISABLE_ALL | 配置 TCP/IP 协议栈取消生成/检查所有检验和 |
- net_type:以太网帧格式类型,详细取值如下表所示。
以太网帧格式类型 | 说明 |
---|---|
NETDEV_TYPE_RAW | RAW 802.3 帧格式 |
NETDEV_TYPE_ETHERNET | Ethernet V2 帧格式 |
- speed:网络传输速度。
- mtu:最大传输单元。
- hwaddr_len:硬件地址长度,必须为 6 或者 8。
- hwaddr:硬件地址。
- drv:硬件操作函数集合。
- priv:私有信息结构体,用于保存驱动自定义结构。
- if_flags:网络接口初始化配置选项,详细取值如下表所示。
接口初始化配置值 | 含义 |
---|---|
IFF_UP | 网络接口使能 |
IFF_BROADCAST | 接收广播数据包 |
IFF_POINTOPOINT | 接口点对点链接 |
IFF_RUNNING | 网络运行状态 |
IFF_MULTICAST | 接收多播数据包 |
IFF_LOOPBACK | 环回接口 |
IFF_NOARP | 没有地址解析协议 |
IFF_PROMISC | 接收所有数据包 |
netdev_funcs 结构体内部封装了网络设备操作函数集合,其详细描述如下:
#include <SylixOS.h>
struct netdev_funcs {
int (*init)(struct netdev *netdev);
int (*up)(struct netdev *netdev);
int (*down)(struct netdev *netdev);
int (*remove)(struct netdev *netdev);
int (*ioctl)(struct netdev *netdev, int cmd, void *arg);
int (*macfilter)(struct netdev *netdev, int op, struct sockaddr *addr);
int (*transmit)(struct netdev *netdev, struct pbuf *p);
void (*receive)(struct netdev *netdev,
int (*input)(struct netdev *, struct pbuf *));
void (*reserved[8]);
};
- init:网络设备初始化函数。
- ioctl:I/O控制函数,用于进行设备特定的 I/O 控制,常见的控制命令如下表所示。
I/O 控制命令 | 说明 |
---|---|
SIOCSIFMTU | 设置最大传输单元 |
SIOCSIFFLAGS | 设置网卡混杂模式 |
SIOCSIFHWADDR | 设置硬件地址 |
- macfilter:组播过滤函数,用于添加/删除组播过滤。
- transmit:网络传输函数,用于数据包的发送。
- receive:网络接收函数,用于数据包的接收。
驱动自定义结构
netdev 结构体中提供了 void*类型的 priv 成员,用于指向用户的自定义结构,用户可以在自定义结构中添加任意数据类型,i.MX6Q 的网卡驱动自定义结构如下:
#include <SylixOS.h>
typedef struct enet {
addr_t ENET_atIobase; /* 网络设备寄存器基地址 */
UINT32 ENET_uiIrqNum; /* MAC中断号 */
UINT16 ENET_ucPhyAddr; /* PHY设备地址 */
UINT32 ENET_uiPhyLinkStatus; /* PHY 连接状态 */
BUFD *ENET_pbufdRxbdBase; /* 接收缓冲描述符指针 */
BUFD *ENET_pbufdTxbdBase; /* 发送缓冲描述符指针 */
BUFD *ENET_pbufdCurRxbd; /* 当前接收描述符 */
BUFD *ENET_pbufdCurTxbd; /* 当前发送描述符 */
LW_SPINLOCK_DEFINE (ENET_slLock);
} ENET;
- ENET_atIobase:寄存器基地址。
- ENET_uiIrqNum:MAC 中断号。
- ENET_ucPhyAddr:PHY 设备地址。
- ENET_uiPhyLinkStatus:PHY 连接状态。
- ENET_pbufdRxbdBase:接收缓冲描述符指针。
- ENET_pbufdTxbdBase:发送缓冲描述符指针。
- ENET_pbufdCurRxbd:当前接收描述符。
- ENET_pbufdCurTxbd:当前发送描述符。
- ENET_slLock:自旋锁。
MII 驱动
网络设备初始化包括对 MAC 控制器,PHY 设备的初始化。MAC 控制器初始化需要根据数据手册进行配置,而 PHY 设备的初始化在内核中已经有比较完善的实现模板,具体代码位于“libsylixos/SylixOS/system/device/mii”下。
PHY 设备初始化需要调用 API_MiiPhyInit 函数,其内部封装了如 PHY 设备扫描、设置连接模式和 PHY 状态监控等操作步骤,其函数原型如下:
#include <SylixOS.h>
INT API_MiiPhyInit (PHY_DEV *pPhyDev)
函数 API_MiiPhyInit 原型分析:
- 此函数成功返回 MII_OK ,失败返回 MII_ERROR 。
- 参数 pPhyDev 为 PHY 设备指针,根据结构体中的配置参数,初始化 PHY 设备。
PHY_DEV 结构体中包含了 PHY 设备初始化过程中需要的参数,其详细描述如下:
#include <SylixOS.h>
typedef struct phy_dev {
LW_LIST_LINE PHY_node; /* Device Header */
PHY_DRV_FUNC *PHY_pPhyDrvFunc;
VOID *PHY_pvMacDrv; /* Mother Mac Driver Control */
UINT32 PHY_uiPhyFlags; /* PHY flag bits */
UINT32 PHY_uiPhyAbilityFlags; /* PHY flag bits */
UINT32 PHY_uiPhyANFlags; /* Auto Negotiation flags */
UINT32 PHY_uiPhyLinkMethod; /* Whether to force link mode */
UINT32 PHY_uiLinkDelay; /* Delay time to wait for Link */
UINT32 PHY_uiSpinDelay; /* Delay time of Spinning Reg */
UINT32 PHY_uiTryMax; /* Max Try times */
UINT16 PHY_usPhyStatus; /* Record Status of PHY */
UINT8 PHY_ucPhyAddr; /* Address of a PHY */
UINT32 PHY_uiPhyID; /* Phy ID */
UINT32 PHY_uiPhyIDMask; /* Phy ID MASK */
UINT32 PHY_uiPhySpeed;
CHAR PHY_pcPhyMode[16]; /* Link Mode description */
} PHY_DEV;
部分成员描述如下:
- PHY_node:PHY 设备链表,当存在多个 PHY 设备时通过链表相连。
- PHY_pPhyDrvFunc:PHY 操作函数。
- PHY_pvMacDrv:指向 MAC 驱动中的变量,并作为参数的指针。
- PHY_uiPhyFlags:PHY 状态标志,其取值如下表所示。
设备状态值 | 含义 |
---|---|
MII_PHY_NWAIT_STAT | 是否等待协商完成 |
MII_PHY_AUTO | 是否允许自动协商 |
MII_PHY_100 | PHY 可用 100M 速度 |
MII_PHY_10 | PHY 可用 10M 速度 |
MII_PHY_FD | PHY 可用全双工 |
MII_PHY_HD | PHY 可用半双工 |
MII_PHY_MONITOR | 允许监控 PHY 状态 |
MII_PHY_INIT | PHY 初始化 |
MII_PHY_1000T_FD | PHY 可用 1000M 全双工 |
MII_PHY_1000T_HD | PHY 可用 1000M 半双工 |
MII_PHY_TX_FLOW_CTRL | PHY 传输流程控制 |
MII_PHY_RX_FLOW_CTRL | PHY 接收流程控制 |
MII_PHY_GMII_TYPE | GMII = 1, MII = 0 |
- PHY_uiPhyANFlags:自动协商标志。
- PHY_uiPhyLinkMethod:PHY 链接模式,其取值如下表所示。
设备状态值 | 含义 |
---|---|
MII_PHY_LINK_UNKNOWN | 未设置链接模式 |
MII_PHY_LINK_AUTO | 设置为自动协商模式 |
MII_PHY_LINK_FORCE | 设置为指定模式 |
- PHY_uiLinkDelay:链接延时时间。
- PHY_uiTryMax:PHY 链接最大尝试次数。
- PHY_usPhyStatus:PHY 的状态寄存器值。
- PHY_ucPhyAddr:PHY 设备地址。
- PHY_uiPhyID:PHY 设备 ID 号。
- PHY_uiPhyIDMask:PHY 设备 ID 掩码。
- PHY_uiPhySpeed:PHY 数据传输速度。
- PHY_pcPhyMode[16]:PHY 工作模式。
PHY_DRV_FUNC 结构体提供了 PHY 操作函数集合,其详细描述如下:
#include <SylixOS.h>
typedef struct phy_drv_func {
FUNCPTR PHYF_pfuncRead; /* phy read function */
FUNCPTR PHYF_pfuncWrite; /* phy write function */
FUNCPTR PHYF_pfuncLinkDown; /* phy status down function */
FUNCPTR PHYF_pfuncLinkSetHook; /* mii phy link set hook func */
} PHY_DRV_FUNC;
- PHYF_pfuncRead:PHY 设备寄存器读操作函数。
- PHYF_pfuncWrite:PHY 设备寄存器写操作函数。
- PHYF_pfuncLinkDown:PHY 设备状态改变回调函数。
- PHYF_pfuncLinkSetHook: PHY 连接状态设置函数。
下面是一个 PHY 设备初始化的例子:
static INT __phyInit (struct netdev *pNetDev)
{
...
pMiidrv = __miiDrvInit(pNetDev); /* 填充PHY_DEV结构体 */
API_MiiPhyInit(&pMiidrv->MIID_phydev); /* 初始化PHY */
...
}
其中 __miiDrvInit 函数填充了 PHY_DEV 结构体,包括 PHY 状态标志、PHY 操作函数、PHY 工作模式等。
后台处理
在网络设备驱动中可采取一定手段来检测和报告链路状态,最常见的方法是采用中断,其次可以对链路状态进行周期性的检测。
下面是一个检测链路状态的例子:
static INT __miiPhyMonitor (VOID)
{
...
if ((phyStatus & MII_SR_LINK) != (oldPhyStatus & MII_SR_LINK)) { /* 若PHY状态发生改变 */
if (PHYF_pfuncLinkDown != LW_NULL) {
API_NetJobAdd((PHYF_pfuncLinkDown), ( pvMacDrv), 0, 0, 0, 0, 0);
PhyStatus = oldPhyStatus;
} /* 添加异步处理队列报告链路状态 */
}
...
}
在 MII 驱动中,已实现定时器检测链路状态,可以通过配置参数启动 PHY 定时器。当 PHY 状态改变时,会调用 PHYF_pfuncLinkDown 回调函数,由用户自己实现。
数据帧接收
网络设备接收数据通过中断触发,在中断处理函数中调用 netdev_notify 函数,最终通过网络设备驱动中的接收函数进行数据接收。
网络设备驱动完成数据帧接收的流程如下:
- 当接收描述符缓冲区收到数据时,触发接收完成中断。
- 在中断处理函数中调用 netdev_notify 函数。
- 根据 netdev_notify 函数参数进行同步/异步数据帧接收,最终调用网络设备驱动中的接收函数。
- 在网络接收函数中获得数据包的有效数据和长度。
- 申请 pbuf 内存空间,将有效数据拷贝至 pbuf,调用(*input)函数提交数据到上层协议栈。
- 设置接收描述符状态,指向下一描述符。
下面是数据帧接收函数的例子:
static irqreturn_t enetIsr (PVOID pvArg, UINT32 uiVector)
{
...
if (status & ENET_EIR_RXF) { /* 获取数据包 */
netdev_notify(pNetDev, LINK_INPUT, 1); /* 进行数据接收 */
writel(ENET_EIR_TXF, atBase + HW_ENET_MAC_EIMR); /* 关闭接收中断 */
}
...
}
static VOID enetCoreRecv (struct netdev *pNetDev,
INT (*input)(struct netdev *, struct pbuf *))
{
pBufd = xxx_pbufdCurRxbd;
while (!((usStatus = xxx_usStatus) & ENET_BD_RX_EMPTY)) { /* 判断描述符是否有效 */
usLen = xxx_usDataLen;
ucFrame = (UINT8 *)xxx_uiBufAddr; /* 获取接收的帧 */
usLen -= 4; /* 除去 FCS */
pBuf = netdev_pbuf_alloc(usLen); /* 申请pbuf内存空间 */
...
pbuf_take(pBuf, ucFrame, (UINT16)usLen); /* 将有效数据拷贝至pbuf */
if (input(pNetDev, pBuf)) { /* 提交数据到协议栈 */
...
}
}
writel(ENET_DEFAULT_INTE, atBase + HW_ENET_MAC_EIMR);
/* 使能接收中断 */
}
数据帧发送
SylixOS 上层协议在发送数据包时,会调用驱动程序中的发送函数,其发送流程如下:
- 获得有效数据和长度。
- 判断发送描述符是否被使用。
- 填充描述符内容,根据数据手册填充其发送描述符状态、数据长度,将有效数据拷贝至描述符缓冲区。
- 设置硬件寄存器,开始进行数据包发送操作。
- 设置指向下一发送描述符。
下面是数据帧发送函数的例子:
static INT enetCoreTx (struct netdev *pNetDev, struct pbuf *pbuf)
{
...
if (usStatus & ENET_BD_TX_READY) { /* 判断当前描述符是否可用 */
return (PX_ERROR);
}
... /* 填充描述符内容 */
pbuf_copy_partial(pbuf, (PVOID)xxx_uiBufAddr, usLen, 0); /* 拷贝pbuf */
writel(ENET_TDAR_TX_ACTIVE, atBase + HW_ENET_MAC_TDAR); /* 设置硬件寄存器,进行数据包发送 */
... /* 设置指向下一发送描述符 */
}
网络接口参数
SylixOS 通过 netdev_add 函数向内核注册一个网络接口,其函数原型如下:
#include <SylixOS.h>
int netdev_add (netdev_t *netdev,
const char *ip,
const char *netmask,
const char *gw,
int if_flags);
函数 netdev_add 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 netdev 是网络设备结构。
- 参数 ip 是网络接口 ip 地址。
- 参数 netmask 是网络接口子网掩码。
- 参数 gw 是网络接口网关。
- 参数 if_flags 是网络接口标志,详细取值如下表所示。
接口初始化配置值 | 含义 |
---|---|
IFF_UP | 网络接口使能 |
IFF_BROADCAST | 接收广播数据包 |
IFF_POINTOPOINT | 接口点对点链接 |
IFF_RUNNING | 网络运行状态 |
IFF_MULTICAST | 接收多播数据包 |
IFF_LOOPBACK | 环回接口 |
IFF_NOARP | 没有地址解析协议 |
IFF_PROMISC | 接收所有数据包 |
下面是一个注册网络接口的例子:
static INT32 __enetRegister (struct netdev *pNetDev)
{
static struct netdev_funcs net_drv = { /* 设置网络驱动函数 */
.init = __enetCoreInit,
.transmit = __enetCoreTx,
.receive = __enetCoreRecv,
};
pNetDev->magic_no = NETDEV_MAGIC; /* 设置网络接口参数 */
...
pNetDev->init_flags = NETDEV_INIT_LOAD_PARAM
| NETDEV_INIT_LOAD_DNS
| NETDEV_INIT_IPV6_AUTOCFG
| NETDEV_INIT_AS_DEFAULT;
pNetDev->chksum_flags = NETDEV_CHKSUM_ENABLE_ALL;
pNetDev->net_type = NETDEV_TYPE_ETHERNET;
pNetDev->speed = PHY_SPEED;
pNetDev->mtu = ENET_MTU_SIZE;
pNetDev->hwaddr_len = ENET_HWADDR_LEN;
pNetDev->drv = &net_drv;
...
netdev_add(pNetDev, /* 添加网络接口 */
cNetIp,
cNetMask,
cNetGw,
IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST);
}
ifparam.ini 文件说明
SylixOS 启动时会读取/etc/ifparam.ini 文件中的网络配置信息,设置网卡 IP 地址等信息。
在/etc 路径下创建 ifparam.ini 文件,其格式范例如下:
[dm9000a]
enable =1
ipaddr =192.168.1.2
netmask=255.255.255.0
gateway=192.168.1.1
default=1
mac=00:11:22:33:44:55
设置为 DHCP 配置,其格式范例如下:
[dm9000a]
enable=1
dhcp=1
mac=00:11:22:33:44:55
- enable:网络接口使能。
- ipaddr:IP 地址配置。
- netmask:子网掩码配置。
- gateway:默认网关配置。
- default:是否为默认路由配置,如果未找到配置默认为使能。
- mac:硬件地址。
- dhcp:是否为 DHCP 配置,如果未找到配置默认为非 DHCP。