前言:此文档针对的是有线网口
一、调试前需要先了解如下知识:
1、申请netdevice
#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \
alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)
alloc_netdev本质调用的是alloc_netdev_mqs原型如下:
struct net_device * alloc_netdev_mqs ( int sizeof_priv, const char *name, void (*setup) (struct net_device *)),unsigned int txqs, unsigned int rxqs);
sizeof_priv:私有数据块大小。
name:设备名字。
setup:回调函数,初始化设备的设备后调用此函数。
txqs:分配的发送队列数量。
rxqs:分配的接收队列数量。
返回值:如果申请成功的话就返回申请到的 net_device 指针,失败的话就返回 NULL
注册网络设备前首先需要申请网络设备,网络设备有很多,包括网卡、路由器、交换机以及CAN网络设备等
,
Linux内核针对以太网设备封装了申请etherdev接口:
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv,count,count)
可以看到最终调用的是这个:
struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs, unsigned int rxqs)
{
return alloc_netdev_mqs(sizeof_priv, "eth%d",NET_NAME_UNKNOWN,ether_setup, txqs, rxqs);
}
其中ether_setup会对net_devie做初步初始化原型如下:
void ether_setup(struct net_device *dev)
{
dev->header_ops = ð_header_ops;
dev->type = ARPHRD_ETHER;
dev->hard_header_len = ETH_HLEN;
dev->min_header_len = ETH_HLEN;
dev->mtu = ETH_DATA_LEN;
dev->min_mtu = ETH_MIN_MTU;
dev->max_mtu = ETH_DATA_LEN;
dev->addr_len = ETH_ALEN;
dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
dev->flags = IFF_BROADCAST|IFF_MULTICAST;
dev->priv_flags |= IFF_TX_SKB_SHARING;
eth_broadcast_addr(dev->broadcast);
}
2、填充net_device_ops
struct net_device_ops {
..............
int (*ndo_open)(struct net_device *dev);
int (*ndo_stop)(struct net_device *dev);
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb),
int (*ndo_set_mac_address)(struct net_device *dev),
...............
};
此结构体成员函数很多,不需要全部都填充,只需要填充如上必要的接口。
ndo_open 函数,打开网络设备的时候此函数会执行,网络驱动程序需要实现此函数,非常重要!会在此函数中做如下工作:
·使能网络外设时钟。
·申请网络所使用的环形缓冲区。
·初始化 MAC 外设。
·绑定接口对应的 PHY。
·如果使用 NAPI 的话要使能 NAPI 模块,通过 napi_enable 函数来使能。
·开启 PHY。
·调用 netif_tx_start_all_queues 来使能传输队列,也可能调用 netif_start_queue 函数。
·……
ndo_stop 函数,关闭网络设备的时候此函数会执行,网络驱动程序也需要实现此
函数。会在此函数中做如下工作:
·停止 PHY。
·停止 NAPI 功能。
·停止发送功能。
·关闭 MAC。
·断开 PHY 连接。
·关闭网络时钟。
·释放数据缓冲区。
·……
ndo_start_xmit 函数,当需要发送数据的时候此函数就会执行,此函数有一个参数为 sk_buff 结构体指针,sk_buff 结构体在 Linux 的网络驱动中非常重要,sk_buff 保存了上层传递给网络驱动层的数据。也就是说,要发送出去的数据都存在了 sk_buff 中。如果发送成功的话此函数返回 NETDEV_TX_OK,如果发送失败了就返回 NETDEV_TX_BUSY,如果发送失败了我们就需要停止队列。
ndo_set_mac_address 函数,此函数用于修改网卡的 MAC 地址,设置 net_device
的 dev_addr 成员变量,并且将 MAC 地址写入到网络外设的硬件寄存器中。
①数据的发送
此函数用于将网络数据发送出去:
int dev_queue_xmit(struct sk_buff *skb);
最终调用的是上面提到net_device_ops中的ndo_start_xmit(struct sk_buff *skb)。
skb:要发送的数据,这是一个 sk_buff 结构体指针,sk_buff 是 Linux 网络驱动中一个非常重要的结构体,网络数据就是以 sk_buff 保存的,各个协议层在 sk_buff 中添加自己的协议头, 最终由底层驱动将 sk_buff 中的数据发送出去。网络数据的接收过程恰好相反,网络底层驱动将接收到的原始数据打包成 sk_buff,然后发送给上层协议,上层会取掉相应的头部,然后将最终的数据发送给用户。
返回值:0 发送成功,负值 发送失败。
②数据的接收
上层接收数据的话使用 netif_rx 函数,但是最原始的网络数据一般是通过轮询、中断或 NAPI的方式来接收。
int netif_rx(struct sk_buff *skb);
skb:保存接收数据的 sk_buff,。
返回值:NET_RX_SUCCESS 成功,NET_RX_DROP 数据包丢弃。
3、sk_buff结构体
sk_buff 是 Linux 网络重要的数据结构,用于管理接收或发送数据包。
struct sk_buff {
union {
struct{
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
union {
ktime_t tstamp;
struct skb_mstamp skb_mstamp;
};
};
struct rb_node rbnode; /* used in netem & tcp stack */
};
struct sock *sk;
struct net_device *dev;
char cb[48] __aligned(8);
unsigned long _skb_refdst;
void (*destructor)(struct sk_buff *skb);
......
unsigned int len, data_len;
__u16 mac_len, hdr_len;
......
__be16 protocol;
__u16 transport_header;
__u16 network_header;
__u16 mac_header;
/* private: */
__u32
headers_end[0];
/* public: */
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head, *data;
unsigned int truesize;
atomic_t users;
};
next 和 prev 分别指向下一个和前一个 sk_buff,构成一个双向链表。
tstamp 表示数据包接收时或准备发送时的时间戳。
sk 表示当前 sk_buff 所属的 Socket。
dev 表示当前 sk_buff 从哪个设备接收到或者发出的。
cb 为控制缓冲区,不管哪个层都可以自由使用此缓冲区,用于放置私有数据。
destructor 函数,当释放缓冲区的时候可以在此函数里面完成某些动作。
len 为实际的数据长度,包括主缓冲区中数据长度和分片中的数据长度。data_len
为数据长度,只计算分片中数据的长度。
mac_len 为连接层头部长度,也就是 MAC 头的长度。
protocol 协议。
transport_header 为传输层头部。
network_header 为网络层头部
mac_header 为链接层头部。
tail 指向实际数据的尾部。
end 指向缓冲区的尾部。
head 指向缓冲区的头部,data 指向实际数据的头部。data 和 tail 指向实际数据的头部和尾部,head 和 end 指向缓冲区的头部和尾部。
1、分配 sk_buff
要使用 sk_buff 必须先分配:
static inline struct sk_buff *alloc_skb(unsigned int size, gfp_t priority)
size:要分配的大小,也就是 skb 数据段大小。
priority:为 GFP MASK 宏,比如 GFP_KERNEL、GFP_ATOMIC 等。
返回值:分配成功的话就返回申请到的 sk_buff 首地址,失败的话就返回 NULL。
static inline struct sk_buff *netdev_alloc_skb(struct net_device *dev, unsigned int length)
dev:要给哪个设备分配 sk_buff。
length:要分配的大小。
返回值:分配成功的话就返回申请到的 sk_buff 首地址,失败的话就返回 NULL。
2、释放 sk_buff
当使用完成以后就要释放掉 sk_buff
void kfree_skb(struct sk_buff *skb)
对于网络设备而言最好使用如下所示释放函数:
void dev_kfree_skb (struct sk_buff *skb)
3、变更 sk_buff
注意:此操作仅仅只是变更skb_buff大小,并未修改数据
①skb_put
在尾部扩展 skb_buff 的数据区,也就是将 skb_buff 的 tail指针 后移 n 个字节,从而将 skb_buff 的 len 增加 n 个字节。
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
skb:要操作的 sk_buff。
len:要增加多少个字节。
返回值:扩展出来的那一段数据区首地址。
②skb_push
在头部扩展 skb_buff 的数据区
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
skb:要操作的 sk_buff。
len:要增加多少个字节。
返回值:扩展完成以后新的数据区首地址。
③sbk_pull
从 sk_buff 的数据区起始位置删除数据
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
skb:要操作的 sk_buff。
len:要删除的字节数。
返回值:删除以后新的数据区首地址。
④skb_reserve
调整缓冲区的头部大小,方法很简单讲 skb_buff 的 data 和 tail 同时后移 n 个字节即可。
static inline void skb_reserve(struct sk_buff *skb, int len)
skb:要操作的 sk_buff。
len:要增加的缓冲区头部大小。
返回值:无。
二、实际代码大致流程:
ch394有两种寄存器类型,通用寄存器和socket寄存器,由数据帧格式中的8位控制字中的bit3区分,如下图。寄存器类型和读写需要做区分,发送缓存和接收缓存不需要作区分,最开始写spi通信接口时就被手册的介绍迷惑了。由于ch394已经集成了phy芯片,所以不需要我们再用mdio去做phy相关初始化。



struct ch394_priv *priv; //自行定义的私有数据
struct net_device *ndev;
1、申请netdevie
ndev = alloc_etherdev(priv)
将申请到的netdev设置到dev
SET_NETDEV_DEV(ndev, dev);
dev_set_drvdata(dev, ndev);
拿到私有数据内存地址
priv = netdev_priv(ndev);
2、填充需要用的私有数据
包括socket0寄存器地址(需要根据实际使用IC设置,这里是为了方便spi数据通信)、中断号、netdev_ops和ethtool_ops。
priv->s0_regs = CH394_S0_REGS;
priv->s0_tx_buf = CH394_TX_MEM_START;
priv->s0_tx_buf_size = CH394_TX_MEM_SIZE;
priv->s0_rx_buf = CH394_RX_MEM_START;
priv->s0_rx_buf_size = CH394_RX_MEM_SIZE;
priv->ndev = ndev;
priv->ops = ops;
priv->irq = irq;
ndev->netdev_ops = &ch394_netdev_ops;
ndev->ethtool_ops = &ch394_ethtool_ops;
3、注册netdev
register_netdev(ndev)
4、设置物理地址
if(mac_addr) //如果设置了物理地址则直接设置到netdev
memcpy(ndev->dev_addr, mac_addr, ETH_ALEN);
else
eth_hw_addr_random(ndev); //没有则使用随机物理地址
5、复位初始化
接下来是做基本的复位初始化,不同ic有不同的操作,具体需要看datasheet,当前ch394需要做如下操作:
static int ch394_hw_reset(struct ch394_priv *priv)
{
u32 rtr;
ch394_reset(priv); //软复位,对通用寄存器0x00写0x80
ch394_disable_intr(priv); //Socket中断使能寄存器0x18写0
ch394_write_macaddr(priv); //将上面ndev->dev_addr设置的物理地址填充到MAC 地址寄存器(MAC)[0x0009-0x000E]
ch394_memory_configure(priv); //设置发送和接收缓存区的大小为最大,并且将缓存区初始化为0为后面使用。
rtr = CH394_RTR;
//设置 TCP 通讯中的重传时间,超时则进行重传操作或者触发超时中断
//当前设置默认值为200ms
if (ch394_read16(priv, rtr) != RTR_DEFAULT)
return -ENODEV;
return 0;
}
6、申请中断线程
NETIF_START_QUEUE:
驱动程序调用这个函数来告诉内核网络子系统,现在可以开始数据包的发送。
NETIF_STOP_QUEUE:
在驱动程序中调用这个函数来告诉内核的网络子系统,当前网卡设备内存不够,不能继续传输数据包,内核要停止数据包的发送。
NETIF_WAKE_QUEUE:
除了实现netif_start_queue的作用外,还会将设备的发送队列加入到CPU的发送队列,并且触发中断处理的下半部来触发数据包发送。
//中断线程化,下半部的一种方式,第二个参数是中断上半部(这里不使用,直接设置为NULL),第三个参数是中断下半部
request_threaded_irq(priv->irq, NULL, ch394_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
netdev_name(ndev), ndev);
static irqreturn_t ch394_interrupt(int irq, void *ndev_instance)
{
struct net_device *ndev = ndev_instance;
struct ch394_priv *priv = netdev_priv(ndev);
int ir = ch394_read(priv, CH394_S0_IR(priv)); //读中断寄存器,看看是产生了什么中断
if (!ir)
return IRQ_NONE;
ch394_write(priv, CH394_S0_IR(priv), ir); //清中断
if (ir & S0_IR_SENDOK) { //ch394网卡发送完成中断
netif_dbg(priv, tx_done, ndev, "tx done\n");
netif_wake_queue(ndev); //会调用ndo_start_xmit发送数据,最终调用到我们自定义的发送队 列ch394_start_tx->ch394_tx_work (具体解析在后面)
}
if (ir & S0_IR_RECV) { //接收中断
ch394_disable_intr(priv);
queue_work(priv->xfer_wq, &priv->rx_work); //将接收数据的工作队列加到系统调度中
}
return IRQ_HANDLED;
}
7、发送数据和接收数据队列
①接收队列
static void ch394_rx_work(struct work_struct *work)
{
struct ch394_priv *priv = container_of(work,struct ch394_priv, rx_work);
struct sk_buff *skb;
while ((skb = ch394_rx_skb(priv->ndev)))
netif_rx(skb); //接收来自网卡的数据包提交到网络协议栈
ch394_enable_intr(priv); //使能socket0中断
}
static struct sk_buff *ch394_rx_skb(struct net_device *ndev)
{
struct ch394_priv *priv = netdev_priv(ndev);
struct sk_buff *skb;
u16 rx_len;
u16 offset;
u8 header[2];
//先获取socket0接收到数据的长度
u16 rx_buf_len = ch394_read16(priv, CH394_S0_RX_RSR(priv));
if (rx_buf_len == 0)
return NULL;
offset = ch394_read16(priv, CH394_S0_RX_RD(priv)); //将接收缓存区读地址拿到
ch394_readbuf(priv, offset, header, 2); //将读地址存储的协议头读取到header
rx_len = get_unaligned_be16(header) - 2; //减去协议头两个字节就是实际数据长度
netdev_alloc_skb_ip_align此函数的作用是申请一个skb描述符及为报文数据buffer分配内存空间。
通常内存空间的分配地址以32位处理器来说是四字节对齐的,但由于以太网的帧格式的
MAC头没有四字节对齐,这样会导致以太网报文的IP头也不符合四字节对齐,
如此会造成IP协议解析校验错误。如果在申请skb描述符时
调用netdev_alloc_skb_ip_align(),分配的报文空间做DMA映射时MAC头
会自动四字节对齐,以使接收到的以太网报文在上传到IP层时能够解析通过。
skb = netdev_alloc_skb_ip_align(ndev, rx_len);
if (unlikely(!skb)) { //接收的数据异常处理
ch394_write16(priv, CH394_S0_RX_RD(priv), offset + rx_buf_len);
ch394_command(priv, S0_CR_RECV);
ndev->stats.rx_dropped++;
return NULL;
}
skb_put(skb, rx_len); //在skb尾部增加rx_len长度的内存
ch394_readbuf(priv, offset + 2, skb->data, rx_len); //将接收缓存区的数据读取到skb
ch394_write16(priv, CH394_S0_RX_RD(priv), offset + 2 + rx_len); //在读取完数据后更新该寄存器值
ch394_command(priv, S0_CR_RECV); //发送socket0接收控制命令
skb->protocol = eth_type_trans(skb, ndev); //用于初始化skb的protocol字段,然后通过 netif_rx(skb)将skb传递给传输层协议。
ndev->stats.rx_packets++;
ndev->stats.rx_bytes += rx_len;
return skb;
}
到这里将从网卡接收到的数据提交到系统网络协议栈的操作已完成。
②发送队列
static void ch394_tx_work(struct work_struct *work)
{
struct ch394_priv *priv =
container_of(work, struct ch394_priv, tx_work);
struct sk_buff *skb = priv->tx_skb;
priv->tx_skb = NULL; //发送前清理数据防止出错
if (WARN_ON(!skb))
return;
ch394_tx_skb(priv->ndev,skb);
}
static void ch394_tx_skb(struct net_device *ndev, struct sk_buff *skb)
{
struct ch394_priv *priv = netdev_priv(ndev);
u16 offset;
ch394_disable_rx(priv);
ch394_read16(priv, CH394_SOCK0_TX_FS0); //读取空闲的发送缓存区长度,此操作也可以忽略(对比了多个内核网卡驱动有些是有做这个操作有些是没有的)
offset = ch394_read16(priv, CH394_S0_TX_WR(priv)); //读取发送缓存区写指针
ch394_writebuf(priv, offset, skb->data, skb->len); //把skb的数据发送到写指针
ch394_write16(priv, CH394_S0_TX_WR(priv), offset + skb->len); //在写入待发送数据后更新该寄存器值
ndev->stats.tx_bytes += skb->len;
ndev->stats.tx_packets++;
dev_kfree_skb(skb);
ch394_command(priv, S0_CR_SEND); //发送数据控制命令
}
数据包的发送和接收到这里就完成了。
8、net_device_ops
上面提到的ch394_netdev_ops到这里就可以去做填充了,这些接口无非就是读写ch394的寄存器,或者调度发送和接收数据的工作队列,具体就不做分析,直接贴上源码。
static const struct net_device_ops ch394_netdev_ops = {
.ndo_open = ch394_open,
.ndo_stop = ch394_stop,
.ndo_start_xmit = ch394_start_tx,
.ndo_tx_timeout = ch394_tx_timeout,
.ndo_set_rx_mode = ch394_set_rx_mode,
.ndo_set_mac_address = ch394_set_macaddr,
.ndo_validate_addr = eth_validate_addr,
};
static void ch394_set_rx_mode(struct net_device *ndev)
{
struct ch394_priv *priv = netdev_priv(ndev);
bool set_promisc = (ndev->flags & IFF_PROMISC) != 0;
if (priv->promisc != set_promisc) {
priv->promisc = set_promisc;
schedule_work(&priv->setrx_work);
}
}
static int ch394_set_macaddr(struct net_device *ndev, void *addr)
{
struct ch394_priv *priv = netdev_priv(ndev);
struct sockaddr *sock_addr = addr;
if (!is_valid_ether_addr(sock_addr->sa_data))
return -EADDRNOTAVAIL;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0))
eth_hw_addr_set(ndev, sock_addr->sa_data);
#else
memcpy(ndev->dev_addr, sock_addr->sa_data, ETH_ALEN);
#endif
ch394_write_macaddr(priv);
return 0;
}
static int ch394_open(struct net_device *ndev)
{
struct ch394_priv *priv = netdev_priv(ndev);
netif_info(priv, ifup, ndev, "enabling\n");
ch394_hw_start(priv); //设置透传模式,打开socket,使能socket中断
netif_start_queue(ndev); //告诉内核数据包可以发送
netif_carrier_on(ndev); //告诉内核需要传递信号
return 0;
}
static int ch394_stop(struct net_device *ndev)
{
struct ch394_priv *priv = netdev_priv(ndev);
netif_info(priv, ifdown, ndev, "shutting down\n");
ch394_hw_close(priv); //关闭中断以及socket
netif_carrier_off(ndev); //告诉内核丢失信号
netif_stop_queue(ndev); //告诉内核停止发送数据包
return 0;
}



2471

被折叠的 条评论
为什么被折叠?



