本文有部分资料和图片摘录自以下链接(侵删):
https://www.cnblogs.com/xiaojiang1025/archive/2017/03/28/6486267.html
https://blog.51cto.com/liucw/1221140
https://blog.youkuaiyun.com/Rong_Toa/article/details/109401935
基础
Linux的网络设备并不使用文件作为用户程序访问网络设备的接口,所以/sys/dev下和/dev下并没有相应的网络设备文件,在Linux中,用户程序最终使用套接字来访问网络设备。Linux的网卡驱动程序处于OSI模型中的数据链路层,他的职责就是将上上层的协议栈传过来的信息通过网卡发送出去。
sk_buff
网络驱动框架中信息的载体, 是网络分层模型中对数据进行层层打包以及层层解包的载体。网络协议栈中各层协议都可以通过对该结构的操作实现本层协议数据的添加或者删除。使用sk_buff结构避免了网络协议栈各层来回复制数据导致的效率低下。
net_device
net_device 结构位于网络驱动程序层的最核心地位,描述了一个网络设备, 其中的 *struct net_device_ops netdev_ops 是操作方法集, 向上提供接口的同时也向下操作硬件。
核心API
- dev_queue_xmit()是网络协议接口层向下发送数据的接口, 内核已经实现, 不需要网络设备驱动实现
- ndo_start_xmit()是网络设备接口层向下发送数据的接口, 位于net_device->net_device_ops, 会被dev_queue_xmit()回调, 需要网络驱动实现
- netif_rx()是网络设备接口层向上发送数据的接口, 不需要网络驱动实现
- 中断处理函数是网络设备媒介层收到数据后向上发送数据的入口, 需要网络驱动实现,最后要调用netif_rx()
数据接收
驱动接收到数据包后调用 netif_rx()函数,把网卡接收到数据提交给协议栈处理
注意:
本项目网络驱动使用了NAPI接收数据,接收流程如下:
数据发送
网关网络驱动源码解析
CPU默认有两个以太网卡,设备树代码如下:
emac0: emac0 {
compatible = "sstar-emac";
interrupts = <GIC_SPI INT_IRQ_EMAC IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_FIQ_LAN_ESD IRQ_TYPE_LEVEL_HIGH>;
clocks = <&CLK_emac_ahb>,<&CLK_emac_tx>,<&CLK_emac_rx>;
reg = <0x1F2A2000 0x800>, <0x1F343C00 0x600>, <0x1F006200 0x600>;
pad = <0x1F203C38 0x0001 0x0000>; // pad selection from 0x0001
phy-handle = <&phy0>;
status = "ok";
mdio-bus@emac0 {
phy0: ethernet-phy@0 {
phy-mode = "mii";
};
};
};
emac1: emac1 {
compatible = "sstar-emac";
interrupts = <GIC_SPI INT_IRQ_EMAC_1 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_FIQ_LAN_ESD IRQ_TYPE_LEVEL_HIGH>;
clocks = <&CLK_emac_ahb>,<&CLK_emac1_tx>,<&CLK_emac1_rx>,<&CLK_emac1_tx_ref>,<&CLK_emac1_rx_ref>;
reg = <0x1F2A2800 0x800>, <0x1F344200 0x600>, <0x00000000 0x000>;
//pad = <0x1F203C38 0x0F00 0x0300>; // pad selection from 0x0100/0x0200/0x0300/0x0400/0x0500/0x0600/0x0700/0x0800/0x0900
pad = <0x1F203C38 0x0F00 0x0300>; // pad selection from 0x0100/0x0200/0x0300/0x0400/0x0500/0x0600/0x0700/0x0800/0x0900
status = "ok";
phy-handle = <&phy1>;
mdio-bus@emac1 {
phy1: ethernet-phy@1 {
phy-mode = "rmii";
};
};
};
因为要从设备树获取设备参数,emac设备挂载在平台下,所以要先注册了个平台驱动,精简代码如下,初始化代码执行顺序是:
mstar_emac_drv_init_module -> platform_driver_register -> mstar_emac_drv_probe -> MDev_EMAC_init
下面代码有较多的注释,而且精简了许多不便于理解驱动流程的代码,例如操作实际硬件的代码。
// net_device 回调函数
static const struct net_device_ops mstar_lan_netdev_ops = {
.ndo_init = MDev_EMAC_ndo_init,
.ndo_uninit = MDev_EMAC_ndo_uninit,
// .ndo_tx_timeout = MDev_EMAC_ndo_tx_timeout,
.ndo_change_mtu = MDev_EMAC_ndo_change_mtu,
.ndo_open = MDev_EMAC_open,
.ndo_stop = MDev_EMAC_close,
.ndo_start_xmit = MDev_EMAC_tx, // 网络接口数据包发送函数
.ndo_set_mac_address = MDev_EMAC_set_mac_address,
.ndo_set_rx_mode = MDev_EMAC_set_rx_mode,
.ndo_do_ioctl = MDev_EMAC_ioctl,
.ndo_get_stats = MDev_EMAC_stats,
.ndo_set_features = MDev_EMAC_set_features,
.ndo_fix_features = MDev_EMAC_fix_features,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = MDev_EMAC_netpoll,
#endif
};
// 获取网络报文并分析,最后上送协议栈
static int MDev_EMAC_rx(struct net_device *dev, int budget)
{
struct emac_handle *hemac = (struct emac_handle*) netdev_priv(dev);
unsigned char *p_recv;
u32 pktlen;
u32 received=0;
struct sk_buff *skb;
struct sk_buff *clean_skb;
rx_desc_queue_t* rxinfo = &(hemac->rx_desc_queue);
do
{
char* pData;
struct rbf_t* desc = &(rxinfo->desc[rxinfo->idx]);
if (!(desc->addr & EMAC_DESC_DONE))
break;
p_recv = BUS2VIRT(CLR_BITS(desc->addr, EMAC_DESC_DONE | EMAC_DESC_WRAP));
pktlen = desc->size & 0x7ffUL; /* Length of frame including FCS */
if (unlikely(((pktlen > EMAC_MTU) || (pktlen < 64)))) // 非法报文
{
desc->addr = CLR_BITS(desc->addr, EMAC_DESC_DONE);
Chip_Flush_MIU_Pipe();
rxinfo->idx++;
continue;
}
if (unlikely(!(clean_skb = alloc_skb (EMAC_PACKET_SIZE_MAX, GFP_ATOMIC))))
goto jmp_rx_exit;
skb = rxinfo->skb_arr[rxinfo->idx];
{
pktlen -= 4; /* Remove FCS */
pData = skb_put(skb, pktlen);
dma_unmap_single(NULL, VIRT2PA(pData), pktlen, DMA_FROM_DEVICE);
}
skb->dev = dev;
skb->protocol = eth_type_trans (skb, dev);
if (0 == MHal_EMAC_FlowControl_TX(hemac))
_MDrv_EMAC_Pause_TX(dev, skb, p_recv);
napi_gro_receive(&hemac->napi, skb); // 转给协议栈处理
received++;
//fill clean_skb into RX descriptor
rxinfo->skb_arr[rxinfo->idx] = clean_skb;
Chip_Flush_MIU_Pipe();
rxinfo->idx++;
if (rxinfo->idx >= rxinfo->num_desc)
rxinfo->idx = 0;
if(received >= budget)
break;
} while(1);
jmp_rx_exit:
if(received>max_rx_packet_count)max_rx_packet_count=received;
rx_packet_cnt += received;
return received;
}
// 报文接收
/*
NAPI (New API) 是Linux内核针对网络数据传输做出的一个优化措施。 其目的是在大量数据传输时, 在收到硬件中断后,通过poll方式将传输过来的数据包统一处理, 通过禁止网络设备中断以减少硬件中断数量 ((Interrupt Mitigation),从而实现更高的数据传输。
*/
static int MDev_EMAC_napi_poll(struct napi_struct *napi, int budget)
{
struct emac_handle *hemac = container_of(napi, struct emac_handle,napi);
struct net_device *dev = hemac->netdev;
int work_done = 0;
int budget_rmn = budget;
unsigned long elapse = 0;
unsigned long packets = 0;
work_done = MDev_EMAC_rx(dev, budget_rmn); // 接收报文处理
// 统计计数 。。。
napi_gro_flush(napi, false);
/* If budget not fully consumed, exit the polling mode */
if (work_done < budget) {
napi_complete(napi);
MHal_EMAC_IntEnable(hemac->hal, EMAC_INT_RCOM, 1);
}
return work_done;
}
// 网卡中断处理函数
irqreturn_t MDev_EMAC_interrupt(int irq,void *dev_id)
{
struct net_device *dev = (struct net_device *) dev_id;
struct emac_handle *hemac = (struct emac_handle*) netdev_priv(dev);
u32 intstatus=0;
hemac->irqcnt++;
hemac->oldTime = getCurMs();
_MDev_EMAC_tx_pump(hemac, 1, 0); // 发送报文
while ((intstatus = MHal_EMAC_IntStatus(hemac->hal)))
{
_MDrv_EMAC_ISR_RBNA(dev, intstatus);
_MDrv_EMAC_ISR_TCOM(dev, intstatus);
_MDrv_EMAC_ISR_DONE(dev, intstatus);
_MDrv_EMAC_ISR_ROVR(dev, intstatus);
_MDrv_EMAC_ISR_RCOM(dev, intstatus);
}
if (netif_queue_stopped (dev) && skb_queue_free(&hemac->skb_queue_tx, 0))
{
MHal_EMAC_IntEnable(hemac->hal, EMAC_INT_TCOM, 0);
if (0 == timer_pending(&hemac->timerFlowTX))
netif_wake_queue(dev);
}
return IRQ_HANDLED;
}
// 发送函数
static int _MDev_EMAC_tx_pump(struct emac_handle *hemac, int bFree, int bPump)
{
spin_lock_irqsave(&hemac->mutexTXQ, flags);
if (bFree)
{
txUsedCnt = MHal_EMAC_TXQ_Used(hemac->hal);
txUsedCntSW = skb_queue_used(&hemac->skb_queue_tx, 0);
ret = txUsedCntSW - txUsedCnt;
for (i = txUsedCnt; i < txUsedCntSW; i++)
{
len = skb_queue_remove(&hemac->skb_queue_tx, NULL, NULL, 1, 0);
hemac->stats.tx_bytes += len;
tx_bytes_per_timer += len;
}
}
if (bPump)
{
int skb_len;
txFreeCnt = skb_queue_free(&hemac->skb_queue_tx, 0);
txPendCnt = skb_queue_used(&hemac->skb_queue_tx, 2);
nPkt = (txFreeCnt < txPendCnt) ? txFreeCnt : txPendCnt;
for (i = 0; i < nPkt; i++)
{
skb_queue_head_inc(&hemac->skb_queue_tx, &skb, &skb_addr, &skb_len, 0);
if (skb_addr)
MHal_EMAC_TXQ_Insert(hemac->hal, skb_addr, skb_len); // 硬件发送队列
}
}
spin_unlock_irqrestore(&hemac->mutexTXQ, flags);
return ret;
}
// 发送函数
static int MDev_EMAC_tx(struct sk_buff *skb, struct net_device *dev)
{
struct emac_handle *hemac = (struct emac_handle*) netdev_priv(dev);
dma_addr_t skb_addr;
int ret = NETDEV_TX_OK;
if (skb_queue_full(&hemac->skb_queue_tx, 1)) // 发送队列满
{
netif_stop_queue(dev);
MHal_EMAC_IntEnable(hemac->hal, EMAC_INT_TCOM, 1);
ret = NETDEV_TX_BUSY;
goto out_unlock;
}
{
int i;
int nr_frags = skb_shinfo(skb)->nr_frags;
int len;
// dma_unmap_single(NULL, VIRT2PA(start), EMAC_MTU, DMA_TO_DEVICE);
if (nr_frags)
{
char* start = kmalloc(EMAC_MTU, GFP_ATOMIC);
char* p = start;
if (!start)
{
ret = NETDEV_TX_BUSY;
goto out_unlock;
}
memcpy(p, skb->data, skb_headlen(skb));
p += skb_headlen(skb);
len = skb_headlen(skb);
for (i = 0; i < nr_frags; i++)
{
struct skb_frag_struct *frag = &skb_shinfo(skb)->frags[i];
memcpy(p, skb_frag_address(frag), skb_frag_size(frag));
p += skb_frag_size(frag);
len += skb_frag_size(frag);
}
if (EMAC_SG_BUF_CACHE)
Chip_Flush_Cache_Range((size_t)start, skb->len);
skb_addr = VIRT2BUS(start);
spin_lock_irqsave(&hemac->mutexTXQ, flags);
skb_queue_insert(&(hemac->skb_queue_tx), (struct sk_buff*)0xFFFFFFFF, (dma_addr_t)skb_addr, skb->len, 1);
spin_unlock_irqrestore(&hemac->mutexTXQ, flags);
// Chip_Flush_Cache_Range((size_t)start, skb->len);
dev_kfree_skb_any(skb);
}
else
{
{
struct sk_buff* skb_tmp = skb_clone(skb, GFP_ATOMIC);
if (!skb_tmp)
{
printk("[%s][%d] skb_clone fail\n", __FUNCTION__, __LINE__);
ret = NETDEV_TX_BUSY;
goto out_unlock;
}
dev_kfree_skb_any(skb);
skb = skb_tmp;
}
skb_addr = VIRT2BUS(skb->data);
spin_lock_irqsave(&hemac->mutexTXQ, flags);
skb_queue_insert(&(hemac->skb_queue_tx), skb, skb_addr, skb->len, 1);
spin_unlock_irqrestore(&hemac->mutexTXQ, flags);
Chip_Flush_Cache_Range((size_t)skb->data, skb->len);
}
}
netif_trans_update(dev);
out_unlock:
_MDev_EMAC_tx_pump(hemac, 1, 1); // 发送报文
return ret;
}
static int MDev_EMAC_setup (struct net_device *dev)
{
struct emac_handle *hemac;
hemac = (struct emac_handle *) netdev_priv(dev);
hemac->netdev = dev;
RetAddr = MDev_EMAC_VarInit(hemac); // 初始化hemac变量,包括获取mac地址
skb_queue_create(&(hemac->skb_queue_tx), MHal_EMAC_TXQ_Size(hemac->hal), MHal_EMAC_TXQ_Size(hemac->hal)+ hemac->txq_num_sw);
MDev_EMAC_HW_init(dev); // 初始化 mac 层寄存器,包括设置 mac 地址,申请 sk_buff 队列
spin_lock_init(&hemac->mutexNetIf);
spin_lock_init(&hemac->mutexPhy);
spin_lock_init(&hemac->mutexTXQ);
ether_setup (dev);
dev->tx_queue_len = EMAC_MAX_TX_QUEUE;
spin_lock_irqsave(&hemac->mutexPhy, flags);
MDev_EMAC_get_mac_address (dev); // Get ethernet address and store it in dev->dev_addr //
MDev_EMAC_update_mac_address (dev); // Program ethernet address into MAC //
MHal_EMAC_enable_mdi(hemac->hal);
spin_unlock_irqrestore(&hemac->mutexPhy, flags);
dev->features |= EMAC_FEATURES;
dev->vlan_features |= EMAC_FEATURES;
hemac->irqcnt=0;
hemac->tx_irqcnt=0;
dev->irq = hemac->irq_emac;
// 申请中断
if (request_irq(dev->irq, MDev_EMAC_interrupt, 0/*SA_INTERRUPT*/, dev->name, dev))
return -EBUSY;
hrtimer_init(&hemac->timerTxWdt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hemac->timerTxWdt.function = _MDev_EMAC_TxWdt_CB;
// 在 /sys/class/net 下创建网络设备系统调用文件
alloc_chrdev_region(&gEthDev, 0, MINOR_EMAC_NUM, "ETH");
hemac->mstar_class_emac_device = device_create(msys_get_sysfs_class(), NULL, MKDEV(MAJOR(gEthDev), hemac->u8Minor), NULL, hemac->name);
dev_set_drvdata(hemac->mstar_class_emac_device, (void*)dev);
device_create_file(hemac->mstar_class_emac_device, &dev_attr_tx_sw_queue_info);
device_create_file(hemac->mstar_class_emac_device, &dev_attr_dlist_info);
device_create_file(hemac->mstar_class_emac_device, &dev_attr_reverse_led);
device_create_file(hemac->mstar_class_emac_device, &dev_attr_check_link_time);
device_create_file(hemac->mstar_class_emac_device, &dev_attr_check_link_timedis);
device_create_file(hemac->mstar_class_emac_device, &dev_attr_info);
device_create_file(hemac->mstar_class_emac_device, &dev_attr_sw_led_flick_speed);
device_create_file(hemac->mstar_class_emac_device, &dev_attr_turndrv);
return 0;
}
static int MDev_EMAC_probe (struct net_device *dev)
{
int detected;
/* Read the PHY ID registers - try all addresses */
detected = MDev_EMAC_setup(dev);
return detected;
}
static int MDev_EMAC_init(struct platform_device *pdev)
{
struct emac_handle *hemac;
int ret;
struct net_device* emac_dev = NULL;
// 申请一个struct net_device,参数是驱动私有参数的大小,
// net_device里面包含一个私有数据结构的指针,会一并申请空间
emac_dev = alloc_etherdev(sizeof(*hemac));
hemac = netdev_priv(emac_dev); // 获取 net_device 里面私有数据的指针
// 初始化 hemac ...
netdev_set_default_ethtool_ops(emac_dev, &sstar_emac_ethtool_ops); // 初始化 ethtool 工具回调函数
MDev_EMAC_dts(emac_dev); // 读设备树
// 根据设备树信息申请资源,初始化硬件 。。。
// NAPI (New API) 在收到硬件中断后,通过poll方式将传输过来的数据包统一处理
netif_napi_add(emac_dev, &hemac->napi, MDev_EMAC_napi_poll, EMAC_NAPI_WEIGHT);
emac_dev->netdev_ops = &mstar_lan_netdev_ops; // 指定net_device 的回调函数
if (MDev_EMAC_probe(emac_dev)) // 探测和初始化 net_device
goto end;
// 初始化PHY 。。。
if ((ret = register_netdev(emac_dev))) // 注册struct net_device
goto end;
platform_set_drvdata(pdev, (void*)emac_dev); // struct net_device 作为平台设备的一个私有数据
return 0;
end:
skb_queue_destroy(&(hemac->skb_queue_tx));
free_netdev(emac_dev);
emac_dev = NULL;
hemac->initstate = ETHERNET_TEST_INIT_FAIL;
return -1;
}
static int mstar_emac_drv_probe(struct platform_device *pdev)
{
int retval=0;
if (retval = MDev_EMAC_init(pdev)) // 网络设备初始化
return retval;
return retval;
}
static struct platform_driver Mstar_emac_driver = {
.probe = mstar_emac_drv_probe,
.remove = mstar_emac_drv_remove,
.suspend = mstar_emac_drv_suspend,
.resume = mstar_emac_drv_resume,
.driver = {
.name = "Sstar-emac",
.of_match_table = mstaremac_of_device_ids,
.owner = THIS_MODULE,
}
};
static int __init mstar_emac_drv_init_module(void)
{
int retval=0;
retval = platform_driver_register(&Mstar_emac_driver);
return retval;
}
static void __exit mstar_emac_drv_exit_module(void)
{
platform_driver_unregister(&Mstar_emac_driver);
}
module_init(mstar_emac_drv_init_module);
module_exit(mstar_emac_drv_exit_module);
end