以太网驱动程序

更新时间:
2024-03-14
下载文档

以太网驱动程序

以太网驱动主要包含以下部分:网络设备初始化、数据帧发送、数据帧接收、后台处理、网络接口注册等。

网络设备结构体

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_RAWRAW 802.3 帧格式
NETDEV_TYPE_ETHERNETEthernet 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_100PHY 可用 100M 速度
MII_PHY_10PHY 可用 10M 速度
MII_PHY_FDPHY 可用全双工
MII_PHY_HDPHY 可用半双工
MII_PHY_MONITOR允许监控 PHY 状态
MII_PHY_INITPHY 初始化
MII_PHY_1000T_FDPHY 可用 1000M 全双工
MII_PHY_1000T_HDPHY 可用 1000M 半双工
MII_PHY_TX_FLOW_CTRLPHY 传输流程控制
MII_PHY_RX_FLOW_CTRLPHY 接收流程控制
MII_PHY_GMII_TYPEGMII = 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 函数,最终通过网络设备驱动中的接收函数进行数据接收。

网络设备驱动完成数据帧接收的流程如下:

  1. 当接收描述符缓冲区收到数据时,触发接收完成中断。
  2. 在中断处理函数中调用 netdev_notify 函数。
  3. 根据 netdev_notify 函数参数进行同步/异步数据帧接收,最终调用网络设备驱动中的接收函数。
  4. 在网络接收函数中获得数据包的有效数据和长度。
  5. 申请 pbuf 内存空间,将有效数据拷贝至 pbuf,调用(*input)函数提交数据到上层协议栈。
  6. 设置接收描述符状态,指向下一描述符。

下面是数据帧接收函数的例子:

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 上层协议在发送数据包时,会调用驱动程序中的发送函数,其发送流程如下:

  1. 获得有效数据和长度。
  2. 判断发送描述符是否被使用。
  3. 填充描述符内容,根据数据手册填充其发送描述符状态、数据长度,将有效数据拷贝至描述符缓冲区。
  4. 设置硬件寄存器,开始进行数据包发送操作。
  5. 设置指向下一发送描述符。

下面是数据帧发送函数的例子:

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。
文档内容是否对您有所帮助?
有帮助
没帮助