本来是做zero-copy的,顺便把分析记录写下来,供大家参考,如果有错误清大家多包涵。只挑重要的来说,一些细节的地方我也不大懂,要看芯片手册才行,我们作软件的就别看那么细了,最重要是把主要流程弄清除。

  1. 系统结构定义

以下定义的结构,要保证长度是32bit的整数,也就是4bytes对齐,在自己添加成员的时候尤其小心。

struct cb 字面理解为control block;

struct nic 网卡的基本信息,该结构是针对单个网卡的,而不是针对网卡驱动整个系统;

  1. 子例程分析

static inline void e100_enable_irq(struct nic *nic)

{

unsigned long flags;

spin_lock_irqsave(&nic->cmd_lock, flags);

writeb(irq_mask_none, &nic->csr->scb.cmd_hi);

spin_unlock_irqrestore(&nic->cmd_lock, flags);

e100_write_flush(nic);

}

static inline void e100_disable_irq(struct nic *nic)

{

unsigned long flags;

spin_lock_irqsave(&nic->cmd_lock, flags);

writeb(irq_mask_all, &nic->csr->scb.cmd_hi);

spin_unlock_irqrestore(&nic->cmd_lock, flags);

e100_write_flush(nic);

}

这两个函数看意思就是把nic指向的网卡的irq打开于关闭,在写寄存器的时候要spin_lock_irq;

e100_write_flush是把内容立即刷新,这里的做法比较简单,就是把pci的总线读一下,这样write的过程就被迫完成了。

  1. 总体分析

初始化过程:

static int e100_hw_init(struct nic *nic)

{

int err;

e100_hw_reset(nic); // 作芯片的复位

DPRINTK(HW, ERR, "e100_hw_init/n");

// 如果是中断期间,返回错误

if(!in_interrupt() && (err = e100_self_test(nic)))

return err;

if((err = e100_phy_init(nic))) // 芯片的初始化,以及后面执行了各种命令

return err;

if((err = e100_exec_cmd(nic, cuc_load_base, 0)))

return err;

if((err = e100_exec_cmd(nic, ruc_load_base, 0)))

return err;

if((err = e100_exec_cb(nic, NULL, e100_load_ucode)))

return err;

if((err = e100_exec_cb(nic, NULL, e100_configure)))

return err;

if((err = e100_exec_cb(nic, NULL, e100_setup_iaaddr)))

return err;

if((err = e100_exec_cmd(nic, cuc_dump_addr,

nic->dma_addr + offsetof(struct mem, stats))))

return err;

if((err = e100_exec_cmd(nic, cuc_dump_reset, 0)))

return err;

e100_disable_irq(nic); // 关闭中断

}

static void e100_watchdog(unsigned long data)

{

// 根据MII的监测工具进行监测,如果发现有网卡动作,则调整统计信息,把网卡设置成up/down状态

mii_ethtool_gset(&nic->mii, &cmd);

if(mii_link_ok(&nic->mii) && !netif_carrier_ok(nic->netdev)) {

DPRINTK(LINK, INFO, "link up, %sMbps, %s-duplex/n",

cmd.speed == SPEED_100 ? "100" : "10",

cmd.duplex == DUPLEX_FULL ? "full" : "half");

} else if(!mii_l    ink_ok(&nic->mii) && netif_carrier_ok(nic->netdev)) {

DPRINTK(LINK, INFO, "link down/n");

}

mii_check_link(&nic->mii);

// 最后,watch_dog不是做一次,所以做完了这次,要用mod_timer启动下一次检查

mod_timer(&nic->watchdog, jiffies + E100_WATCHDOG_PERIOD);

}

发包过程:

static inline int e100_tx_clean(struct nic *nic) // 对发包队列进行清理

{

struct cb *cb;

int tx_cleaned = 0;

spin_lock(&nic->cb_lock); // 要上锁,其实我觉得这里会影响速度;但是100M网卡,影响也不大,对1000M网卡,这样肯定不行

DPRINTK(TX_DONE, DEBUG, "cb->status = 0x%04X/n",

nic->cb_to_clean->status);

/* Clean CBs marked complete */

for(cb = nic->cb_to_clean;

cb->status & cpu_to_le16(cb_complete); // 把CPU字节转成机器字节

cb = nic->cb_to_clean = cb->next) {

if(likely(cb->skb != NULL)) {

nic->net_stats.tx_packets++;

nic->net_stats.tx_bytes += cb->skb->len;

pci_unmap_single( nic->pdev, // 解除PCI通道的DMA映射

le32_to_cpu(cb->u.tcb.tbd.buf_addr),

le16_to_cpu(cb->u.tcb.tbd.size),

PCI_DMA_TODEVICE);

dev_kfree_skb_any(cb->skb); // 才可以释放skb

cb->skb = NULL; // 把指针设置为空,要用这个作判断,所以还是C++好

tx_cleaned = 1;

}

cb->status = 0;

nic->cbs_avail++;

}

spin_unlock(&nic->cb_lock);

/* Recover from running out of Tx resources in xmit_frame */

if(unlikely(tx_cleaned && netif_queue_stopped(nic->netdev)))

netif_wake_queue(nic->netdev); // 唤醒该网卡的等待队列

return tx_cleaned;

}

控制队列的操作,原理和上面一样:

static void e100_clean_cbs(struct nic *nic)

static int e100_alloc_cbs(struct nic *nic)

启动接收过程

static inline void e100_start_receiver(struct nic *nic, struct rx *rx)

给收包过程分配skb,这个是非常重要的过程,主要完成skb的分配工作,如果rx队列没有skb,则new一个,否则把状态同步一下,然后直接使用旧的skb,用于提高效率。分配好的skb要作pci_map动作,就是把内存挂在网卡的DMA通道,等有中断发生,内存就是网络数据包了,效验的动作在后面会作。

static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)

{

// 分配skb

if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))

return -ENOMEM;

/* Align, init, and map the RFD. */

rx->skb->dev = nic->netdev;

skb_reserve(rx->skb, NET_IP_ALIGN); // 保留IP对齐,用于VLAN的偏移,一般是2个字节

    memcpy(rx->skb->data, &nic->blank_rfd, sizeof(struct rfd));

// 在skb->data保留了一段内存作RFD,应该是状态寄存器,e100网卡的DMA通道前面的内存是用于做状态标志的,实际测试是16个字节

rx->dma_addr = pci_map_single(nic->pdev, rx->skb->data,

RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);

// 映射到PCI的DMA通道,这样有中断发生就可以直接送到内存(skb->data)

if(pci_dma_mapping_error(rx->dma_addr)) {

dev_kfree_skb_any(rx->skb);

rx->skb = 0;

rx->dma_addr = 0;

return -ENOMEM;

}

/* Link the RFD to end of RFA by linking previous RFD to

* this one, and clearing EL bit of previous. */

if(rx->prev->skb) { // 如果prev队列没有给释放,太好了,直接把状态清除就可以了

struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->data;

put_unaligned(cpu_to_le32(rx->dma_addr),

(u32 *)&prev_rfd->link);

wmb();

prev_rfd->command &= ~cpu_to_le16(cb_el);

       pci_dma_sync_single_for_device(nic->pdev, rx->prev->dma_addr,

       sizeof(struct rfd), PCI_DMA_TODEVICE);

// DMA通道同步,把状态寄存器与外面的内存同步一下

}

return 0;

}

// 主要的收包过程,有中断发生后,这个函数把接收的包首先解除PCI_DMA映射,然后纠错,最后要把包送到协议栈

static inline int e100_rx_indicate(struct nic *nic, struct rx *rx,

unsigned int *work_done, unsigned int work_to_do)

{

struct sk_buff *skb = rx->skb;

struct rfd *rfd = (struct rfd *)skb->data;

u16 rfd_status, actual_size;

if(unlikely(work_done && *work_done >= work_to_do))

return -EAGAIN;

/* Need to sync before taking a peek at cb_complete bit */

// 同步一下状态,也就是skb前16字节的内存,后面根据rdf_status判断包是否收全了

    pci_dma_sync_single_for_cpu(nic->pdev, rx->dma_addr,

                sizeof(struct rfd), PCI_DMA_FROMDEVICE);

    rfd_status = le16_to_cpu(rfd->status);

DPRINTK(RX_STATUS, DEBUG, "status=0x%04X/n", rfd_status);

/* If data isn't ready, nothing to indicate */

if(unlikely(!(rfd_status & cb_complete)))

return -ENODATA;

/* Get actual data size */

actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;

// 判断包是否收全

if(unlikely(actual_size > RFD_BUF_LEN - sizeof(struct rfd)))

actual_size = RFD_BUF_LEN - sizeof(struct rfd);

/* Get data */

// 解除DMA映射,这样skb->data就可以自由了

    pci_unmap_single(nic->pdev, rx->dma_addr,

                RFD_BUF_LEN, PCI_DMA_FROMDEVICE);

/* this allows for a fast restart without re-enabling interrupts */

if(le16_to_cpu(rfd->command) & cb_el)

nic->ru_running = RU_SUSPENDED;

/* Pull off the RFD and put the actual data (minus eth hdr) */

skb_reserve(skb, sizeof(struct rfd)); // 如果是VLAN,把指针调整一下

skb_put(skb, actual_size);

skb->protocol = eth_type_trans(skb, nic->netdev);

// 作错包乱包检查

if(unlikely(!(rfd_status & cb_ok))) {

/* Don't indicate if hardware indicates errors */

nic->net_stats.rx_dropped++;

dev_kfree_skb_any(skb);

} else if(actual_size > ETH_DATA_LEN + VLAN_ETH_HLEN) {

/* Don't indicate oversized frames */

nic->rx_over_length_errors++;

nic->net_stats.rx_dropped++;

dev_kfree_skb_any(skb);

} else {

// 终于正确收到了,统计数据都要作下增加

nic->net_stats.rx_packets++;

nic->net_stats.rx_bytes += actual_size;

nic->netdev->last_rx = jiffies;

// 送到协议栈

    #ifdef CONFIG_E100_NAPI

        netif_receive_skb(skb); // NAPI的poll方式,使用软中断

   #else

       netif_rx(skb); // 普通的中断方式,使用硬中断

   #endif

if(work_done)

(*work_done)++;

}

rx->skb = NULL;

return 0;

}

// 收报skb的清除

static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,

unsigned int work_to_do)

// 下面这两个函数针对收报队列的管理,也就是调用e100_rx_clean, e100_rx_alloc_skb,用户状态的链表,实际上比较简单,如果哪个给送走了,就检查,再分配一个;

// 因为e100是百兆网卡,所以只有一个用户太的skb管理队列,e1000系列的则硬件中维护另外一个队列,一次可以map 1024个skb

static void e100_rx_clean_list(struct nic *nic)

static int e100_rx_alloc_list(struct nic *nic)

// 初始化中断

static irqreturn_t e100_intr(int irq, void *dev_id, struct pt_regs *regs)

设置POLL的函数:

static int e100_poll(struct net_device *netdev, int *budget)

static void e100_netpoll(struct net_device *netdev)

网卡启动:

对应ifconfig eth0 up这样的命令

static int e100_up(struct nic *nic)

{

int err;

if((err = e100_rx_alloc_list(nic))) // 分配收包队列

return err;

if((err = e100_alloc_cbs(nic))) // 分配控制队列

goto err_rx_clean_list;

if((err = e100_hw_init(nic))) // 硬件初始化

goto err_clean_cbs;

e100_set_multicast_list(nic->netdev); // 多播?

e100_start_receiver(nic, 0); // 准备工作

mod_timer(&nic->watchdog, jiffies); // 时间狗,自动检查网卡状态

if((err = request_irq(nic->pdev->irq, e100_intr, SA_SHIRQ,

nic->netdev->name, nic->netdev))) // 请求IRQ分配

goto err_no_irq;

netif_wake_queue(nic->netdev); // 唤醒网络队列,通知核心,这个网卡启动了

#ifdef CONFIG_E100_NAPI

netif_poll_enable(nic->netdev); // NAPI方式,把pool使能

/* enable ints _after_ enabling poll, preventing a race between

* disable ints+schedule */

#endif

e100_enable_irq(nic); // 使能中断,NAPI方式也需要,普通方式更需要

return 0;

err_no_irq:

del_timer_sync(&nic->watchdog);

err_clean_cbs:

e100_clean_cbs(nic);

err_rx_clean_list:

e100_rx_clean_list(nic);

return err;

}

Ifconfig eth0 down

static void e100_down(struct nic *nic) // 对应e100_up的逆向操作,比较简单

{

#ifdef CONFIG_E100_NAPI

/* wait here for poll to complete */

netif_poll_disable(nic->netdev);

#endif

netif_stop_queue(nic->netdev);

e100_hw_reset(nic);

free_irq(nic->pdev->irq, nic->netdev);

del_timer_sync(&nic->watchdog);

netif_carrier_off(nic->netdev);

e100_clean_cbs(nic);

e100_rx_clean_list(nic);

}

Ethtools对应的函数,这里都列出来了

static struct ethtool_ops e100_ethtool_ops = {

.get_settings = e100_get_settings,

.set_settings = e100_set_settings,

.get_drvinfo = e100_get_drvinfo,

.get_regs_len = e100_get_regs_len,

.get_regs = e100_get_regs,

.get_wol = e100_get_wol,

.set_wol = e100_set_wol,

.get_msglevel = e100_get_msglevel,

.set_msglevel = e100_set_msglevel,

.nway_reset = e100_nway_reset,

.get_link = e100_get_link,

.get_eeprom_len = e100_get_eeprom_len,

.get_eeprom = e100_get_eeprom,

.set_eeprom = e100_set_eeprom,

.get_ringparam = e100_get_ringparam,

.set_ringparam = e100_set_ringparam,

.self_test_count = e100_diag_test_count,

.self_test = e100_diag_test,

.get_strings = e100_get_strings,

.phys_id = e100_phys_id,

.get_stats_count = e100_get_stats_count,

.get_ethtool_stats = e100_get_ethtool_stats,

};

// 对应标准网卡驱动程序的一些封装函数

static int e100_open(struct net_device *netdev)

static int e100_close(struct net_device *netdev)

static int __devinit e100_probe(struct pci_dev *pdev,

const struct pci_device_id *ent)

static void __devexit e100_remove(struct pci_dev *pdev)

static int e100_suspend(struct pci_dev *pdev, u32 state)

static int e100_resume(struct pci_dev *pdev)

static void e100_shutdown(struct device *dev)

// 这个是网卡驱动的函数表,每个网卡都有的

static struct pci_driver e100_driver = {

.name = DRV_NAME,

.id_table = e100_id_table,

.probe = e100_probe,

.remove = __devexit_p(e100_remove),

#ifdef CONFIG_PM

.suspend = e100_suspend,

.resume = e100_resume,

#endif

#if ( LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) )

.driver = {

.shutdown = e100_shutdown,

}

#endif

};

static int __init e100_init_module(void)

{

if(((1 << debug) - 1) & NETIF_MSG_DRV) {

printk(KERN_INFO PFX "%s, %s/n", DRV_DESCRIPTION, DRV_VERSION);

printk(KERN_INFO PFX "%s/n", DRV_COPYRIGHT);

}

return pci_module_init(&e100_driver);

}

static void __exit e100_cleanup_module(void)

{

pci_unregister_driver(&e100_driver);

}

// 模块标准函数

module_init(e100_init_module);

module_exit(e100_cleanup_module);

Intel E100 网卡驱动实例分析相关推荐

  1. linux驱动架构变化,Linux网卡驱动架构分析

    一.网卡驱动架构 由上到下层次依次为:应用程序→系统调用接口→协议无关接口→网络协议栈→设备无关接口→设备驱动. 二.重要数据结构 1.Linux内核中每一个网卡由一个net_device结构来描述. ...

  2. Server2012R2下安装intel 82579v网卡驱动

    不知道有没有和我一样喜欢使用新系统的IT人士,在使用新系统的过程中,总会遇到这样那样的问题.两周前在安装windows server 2012时,无法安装有线网卡驱动.在使用驱动精灵驱动了一下后出现计 ...

  3. Linux网卡驱动(1)-网卡驱动架构分析

    1.Linux网络子系统 网络子系统采用分层的结构: 我们这里研究内核空间即可,在内核空间分成5层,分别是: 1.系统调用接口,它面向的客户是应用层序,为应用程序提供访问网络子系统的统一方法,比如说s ...

  4. intel x520网卡驱动_手工编译linux桌面内核(二)——硬件驱动的配置 下篇

    前言: 前面的方法讲完了,接下来我们来看看实例(我自己电脑的配置). 这里我只打算列出几项重要的驱动配置来,其它的请自行查阅gentoo wiki! 再次强调,这是我自己电脑的硬件驱动配置,不可能完全 ...

  5. 6.S081 lab: networking e1000 网卡驱动 附 Linux 网卡驱动编写分析

    本文是 6.S081 操作系统课程学习最后一个 lab,编写一个 intel 的 e1000 网卡的驱动在 xv6 下.需要复习知识有:操作系统知识,计算机组成原理 DMA 相关,循环缓冲区的概念,e ...

  6. linux kernel有线网卡驱动enc28j60分析 一

    1.为了更好低学习linux的网络驱动架构,本文选择分析linux kernel下的有线网卡驱动enc28j60来学习网络驱动架构. enc28j60是一个10/100Mb的有线网卡,适用于嵌入式设备 ...

  7. i219v微星 驱动_Windows Server 2019 安装 Intel I219V 网卡驱动

    1. 从主板官网下载用于 Windows 10 的网卡驱动 2. 我这边下的是 23.0,并不是 23.5 3. 下载后解压,找到 Intel_lan/PRO1000/Winx64/NDIS65 文件 ...

  8. Ubuntu18.04离线安装Intel I219-V网卡驱动解决有线网络无法连接网卡不识别的问题

    TOC I. 系统和硬件 II. 问题 III. 解决方法 i. 驱动安装方法 ii. 离线安装包 I. 系统和硬件 Win10 和 Ubuntu18.04 UEFI下双系统 华硕Z270pro,I2 ...

  9. Windows Server安装华硕主板的Intel i219-V网卡驱动

    目录 1. 存在问题 1.1 安装完Windows Server 2016没有网卡驱动 2. 解决方法 2.1 到华硕官网下载驱动拷贝到本机 2.2 点击打开:仪表板-工具-计算机管理 2.3 查看& ...

  10. 关于1588 PTP的IGB网卡驱动代码分析

    最近在学习PTP的网卡驱动实现部分内容,所以分析一下IGB的相关代码,供大家参考. 1 初始化igb_ptp_init igb_probe => igb_ptp_init,下面看一下igb_pt ...

最新文章

  1. 机器学习-特征工程中的特征选择
  2. 那个悲伤的朋友,去了一趟菜场竟然活过来了
  3. 利用VMware Infrastructure SDK编程控制虚拟机集群(3)
  4. 前台跨站点获取session
  5. IOS发布应用照片大小
  6. MATLAB中的FFT函数以及频谱泄露
  7. 关于php多维数组,PHP多维数组
  8. selenium+python实现登QQ邮箱并发送邮件自动化
  9. oracle11g基于bootstrap$中的ind$表损坏系列五
  10. 计算机网络(谢希仁-第八版)第一章习题全解
  11. java开发工程师p2级别_java开发工程师p2级别_Java程序员等级怎么划分?
  12. nginx 按天分割日志
  13. 携程是如何做React Native优化的
  14. 虚拟主播怎么做出来的?今日安利:AI虚拟人物怎么弄?
  15. VPP系统 接口启用DHCP
  16. 干货:O2O美团外卖四部曲
  17. python隐秘的角落——笛卡尔之心
  18. 金蝶专业版怎么反过账当月_金蝶kis专业版的反过账是怎么操作的?
  19. 帽子设计作品——蒸汽朋克的乌托邦,机械配件的幻想世界!
  20. thinkphp文件上传以及 unable to create temporary file in 警告

热门文章

  1. Alice and the List of Presents CodeForces - 1236B 数学推导
  2. 随笔三(触动心灵的那些话)
  3. “已取消到该网页的导航” chm文件无法显示错误 解决方法
  4. Golang学习(十四)数组
  5. 解决批改网写英语作文不能粘贴问题
  6. SAP ABAP MOVE-CORRESPONDING ... TO ...的使用
  7. Django | ORM choices参数详解
  8. 小菜鸟szx的测试博文
  9. playwright-python 截图、录制视频、录制接口(二)
  10. View事件分发机制分析