PCI总线概念:

PCI总线不只是一种排线方式,它还定义了一整套计算机不同部分交互的规则。PCI设备在启动时自动被配置,相关驱动必须读取这些配置信息来完成初始化。

linux实现了PCI domains。一个domain 支持 256 buses。每个bus最多支持32个设备,一个设备可以是一个多功能线路板(如:包含一个声卡和一个CD-ROM),此多功能板最多支持8个功能。每个功能由一个16位的地址进行标识(对应于pci_dev)。可用lspci命令查看,输出为bus:device; bus:device.function; domain:bus:device.function。以后我们说的设备指的就是一个功能。

和设备相关的有三个地址空间:memory locations,I/O ports,configuration registers。前两个被一个PCI bus上的所有设备共享。访问前两个地址空间可以用一般的函数,如inb,readb等,访问configuration registers提供了专门的函数。在初始化时,不同设备的I/O space 被映射到了不同的地址空间,这一信息可以访问configuration registers得到。

注意PCI registers总是little-endian。下面是一些主要的registers:

  1. vendorID
    16位,设备厂家的标识
  2. deviceID 
    16位,由设备厂家提供的设备标识
  3. class
      每个设备都属于一种class。这是一个24位的值,高8位表示一个group,如:ethernet 和 token ring 都属于 network group
  4. subsystem vendorID
    对设备进一步标识。

这里的网卡是一个PCI设备,作为一个驱动,首先要向内核申明自己都支持什么设备类型:

[ drivers/net/ethernet/realtek/8139cp.c ]

/* 驱动支持的硬件设备列表*/
static DEFINE_PCI_DEVICE_TABLE(cp_pci_tbl) = {{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK,  PCI_DEVICE_ID_REALTEK_8139), },{ PCI_DEVICE(PCI_VENDOR_ID_TTTECH,   PCI_DEVICE_ID_TTTECH_MC322), },{ },
};
MODULE_DEVICE_TABLE(pci, cp_pci_tbl); // 在内核中注册此列表,当内核在访问一个设备时,就可以通过这个列表找到相关的驱动

由于是PCI设备,网卡驱动当然要作为PCI驱动注册到内核中。

[ drivers/net/ethernet/realtek/8139cp.c ]

/* 这个网卡驱动首先是一个PCI驱动,此结构用来把PCI驱动注册到内核中*/
static struct pci_driver cp_driver = {.name         = DRV_NAME,   // 模块名称.id_table     = cp_pci_tbl, // 此驱动支持的硬件设备列表.probe        = cp_init_one,    // 此驱动被加载时的初始化函数.remove       = cp_remove_one, // 此驱动被卸载时调用的函数
/* 如果支持电源管理*/
#ifdef CONFIG_PM.resume       = cp_resume, // 激活时调用的函数.suspend      = cp_suspend, // 挂起时调用的函数
#endif
};/* 注册cp_driver到内核中*/
static int __init cp_init (void)
{
#ifdef MODULEpr_info("%s", version);
#endifreturn pci_register_driver(&cp_driver);
}/* 从内核中注销cp_driver*/
static void __exit cp_exit (void)
{pci_unregister_driver (&cp_driver);
}module_init(cp_init); // 内核启动或是模块加载时调用 cp_init
module_exit(cp_exit); // 内核或模块卸载时调用 cp_exit

驱动模块在使用之前必须初始化:
[ drivers/net/ethernet/realtek/8139cp.c ]

/* 驱动被加载时的初始化函数* pdev : PCI硬件设备,这里是网卡* ent  : 驱动支持的硬件列表*/
static int cp_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
{struct net_device *dev;    // 网络设备struct cp_private *cp;   // 驱动的私有数据int rc;void __iomem *regs;    // PCI映射到物理内存的起始地址对应的内核的虚地址resource_size_t pciaddr; // PCI映射到物理内存的起始地址unsigned int addr_len, i, pci_using_dac;#ifndef MODULEstatic int version_printed;if (version_printed++ == 0)pr_info("%s", version);
#endif/* pdev->vendor是16位的设备厂家的标识* pdev->device是16位的由设备厂家提供的设备标识* pdev->revision是版本号* 这些值都是内核从PCI设备的寄存器中读取出来的。*/if (pdev->vendor == PCI_VENDOR_ID_REALTEK &&pdev->device == PCI_DEVICE_ID_REALTEK_8139 && pdev->revision < 0x20) {dev_info(&pdev->dev,"This (id %04x:%04x rev %02x) is not an 8139C+ compatible chip, use 8139too\n",pdev->vendor, pdev->device, pdev->revision);return -ENODEV;}/* 分配网络设备,大小为驱动的私有数据的大小*/dev = alloc_etherdev(sizeof(struct cp_private));if (!dev)return -ENOMEM;/* 把新建的net_device对象加到树中* 内核中所有的设备和驱动都被组织在一棵树中*/SET_NETDEV_DEV(dev, &pdev->dev);cp = netdev_priv(dev);    // 私有数据cp->pdev = pdev;cp->dev = dev;cp->msg_enable = (debug < 0 ? CP_DEF_MSG_ENABLE : debug);spin_lock_init (&cp->lock);cp->mii_if.dev = dev;cp->mii_if.mdio_read = mdio_read;cp->mii_if.mdio_write = mdio_write;cp->mii_if.phy_id = CP_INTERNAL_PHY;cp->mii_if.phy_id_mask = 0x1f;cp->mii_if.reg_num_mask = 0x1f;cp_set_rxbufsize(cp);  // 设置缓冲区大小,将cp->rx_buf_sz设为PKT_BUF_SZ(1536字节)rc = pci_enable_device(pdev); // 激活设备if (rc)goto err_out_free;/* 设置memory-write-invalidate* 就是当把一大段数据(大于一个cache line)写入到PCI设备时,如果此时cache为脏(cache中的数据还没有写入PCI设备),* 就直接把数据写入到PCI设备,同时将cache中的脏标记去掉。*/rc = pci_set_mwi(pdev);if (rc)goto err_out_disable;/* 通知内核设备对应的内存资源和IO己经被占用,其它的PCI设备不要再使用这一区域了*/rc = pci_request_regions(pdev, DRV_NAME);if (rc)goto err_out_mwi;pciaddr = pci_resource_start(pdev, 1); // 得到PCI映射到物理内存的起始地址if (!pciaddr) {rc = -EIO;dev_err(&pdev->dev, "no MMIO resource\n");goto err_out_res;}if (pci_resource_len(pdev, 1) < CP_REGS_SIZE) {   // 得到PCI映射到物理内存的长度,长度不小于(0xff + 1)rc = -EIO;dev_err(&pdev->dev, "MMIO resource (%llx) too small\n",(unsigned long long)pci_resource_len(pdev, 1));goto err_out_res;}/* Configure DMA attributes. */if ((sizeof(dma_addr_t) > 4) && // DMA地址是64位的!pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) &&!pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) {pci_using_dac = 1;} else {pci_using_dac = 0;rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));if (rc) {dev_err(&pdev->dev,"No usable DMA configuration, aborting\n");goto err_out_res;}rc = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));if (rc) {dev_err(&pdev->dev,"No usable consistent DMA configuration, aborting\n");goto err_out_res;}}// 设置C+ mode 命令cp->cpcmd = (pci_using_dac ? PCIDAC : 0) |   // DMA地址是否为64位PCIMulRW | RxChkSum | CpRxOn | CpTxOn;    // Enable PCI read/write multiple // Rx checksum offload enable // Rx mode enable // Tx mode enabledev->features |= NETIF_F_RXCSUM;     /* Receive checksumming offload */dev->hw_features |= NETIF_F_RXCSUM;   /* Receive checksumming offload */regs = ioremap(pciaddr, CP_REGS_SIZE);   // 物理地址映射到内核的虚地址空间if (!regs) {rc = -EIO;dev_err(&pdev->dev, "Cannot map PCI MMIO (%Lx@%Lx)\n",(unsigned long long)pci_resource_len(pdev, 1),(unsigned long long)pciaddr);goto err_out_res;}cp->regs = regs;cp_stop_hw(cp);   // 初始化硬件寄存器/* read MAC address from EEPROM * 从EEPROM中读取MAC地址*/addr_len = read_eeprom (regs, 0, 8) == 0x8129 ? 8 : 6;/* 读取3次,每次2字节,把6字节的MAC地址读到了 dev->dev_addr中*/for (i = 0; i < 3; i++)((__le16 *) (dev->dev_addr))[i] =cpu_to_le16(read_eeprom (regs, i + 7, addr_len));dev->netdev_ops = &cp_netdev_ops;   // 网络设备的各项操作接口/* 当支持NAPI时,执行poll操作的函数为cp_rx_poll* 将cp->napi挂载到了dev上*/netif_napi_add(dev, &cp->napi, cp_rx_poll, 16);   dev->ethtool_ops = &cp_ethtool_ops;dev->watchdog_timeo = TX_TIMEOUT;    // 发送超时:(6*HZ)/* Transmit VLAN CTAG HW acceleration * Receive VLAN CTAG HW acceleration */dev->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX;if (pci_using_dac)  // 64位dev->features |= NETIF_F_HIGHDMA; /* Can DMA to high memory. *//* disabled by default until verified *//* Scatter/gather IO.  * Can checksum TCP/UDP over IPv4.* ... TCPv4 segmentation * Transmit VLAN CTAG HW acceleration * Receive VLAN CTAG HW acceleration */dev->hw_features |= NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX;/* Scatter/gather IO.  * Can checksum TCP/UDP over IPv4.* ... TCPv4 segmentation * Can DMA to high memory. */dev->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |NETIF_F_HIGHDMA;rc = register_netdev(dev);    // 内核注册net_device对象if (rc)goto err_out_iomap;netdev_info(dev, "RTL-8139C+ at 0x%p, %pM, IRQ %d\n",regs, dev->dev_addr, pdev->irq);pci_set_drvdata(pdev, dev);  // PCI设备与dev关联/* enable busmastering and memory-write-invalidate * 开启DMA*/pci_set_master(pdev);/* 如果网卡支持wake-on-LAN,进行一些设置* wadk-on-LAN是指一种电源管理功能。主机睡眠时,当网络有一些活动,就会把主机唤醒。*/if (cp->wol_enabled)cp_set_d3_state (cp); /* Put the board into D3cold state and wait for WakeUp signal */return 0;err_out_iomap:iounmap(regs);
err_out_res:pci_release_regions(pdev);
err_out_mwi:pci_clear_mwi(pdev);
err_out_disable:pci_disable_device(pdev);
err_out_free:free_netdev(dev);return rc;
}

下面是一些在初始化函数中要调用的函数:

分配网络设备net_device

[ include/linux/etherdevice.h ]

#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)

[ net/ethernet/eth.c ]

/*** alloc_etherdev_mqs - Allocates and sets up an Ethernet device* @sizeof_priv: Size of additional driver-private structure to be allocated*  for this Ethernet device* @txqs: The number of TX queues this device has.* @rxqs: The number of RX queues this device has.** Fill in the fields of the device structure with Ethernet-generic* values. Basically does everything except registering the device.** Constructs a new net device, complete with a private data area of* size (sizeof_priv).  A 32-byte (not bit) alignment is enforced for* this private data area.*/struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs,unsigned int rxqs)
{return alloc_netdev_mqs(sizeof_priv, "eth%d", ether_setup, txqs, rxqs);
}
EXPORT_SYMBOL(alloc_etherdev_mqs);

[ net/core/dev.c ]

/*** alloc_netdev_mqs - allocate network device* @sizeof_priv:  size of private data to allocate space for* @name:     device name format string*  @setup:        callback to initialize device*  @txqs:     the number of TX subqueues to allocate* @rxqs:     the number of RX subqueues to allocate**    Allocates a struct net_device with private data area for driver use*    and performs basic initialization.  Also allocates subqueue structs*    for each queue on the device.*/
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,void (*setup)(struct net_device *),unsigned int txqs, unsigned int rxqs)
{struct net_device *dev;    // 网络设备size_t alloc_size;   // 实际分配的大小struct net_device *p; // 指向分配的内存BUG_ON(strlen(name) >= sizeof(dev->name));/* 传输队列大小不能小于1*/if (txqs < 1) {pr_err("alloc_netdev: Unable to allocate device with zero queues\n");return NULL;}/* 是否启用sysfs虚拟文件系统。sysfs以更整齐更直观的方式向用户展示了内核的各种参数* /proc将会向sysfs迁移*/
#ifdef CONFIG_SYSFS/* 接收队列大小不能小于1*/if (rxqs < 1) {pr_err("alloc_netdev: Unable to allocate device with zero RX queues\n");return NULL;}
#endif/* 内存大小为net_device结构大小加上传递进来的值的大小* 要32字节对齐,对齐的公式为  ( xx + 31  ) & ( ~31 )*/alloc_size = sizeof(struct net_device);if (sizeof_priv) {/* ensure 32-byte alignment of private area */alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);alloc_size += sizeof_priv;}/* ensure 32-byte alignment of whole construct */alloc_size += NETDEV_ALIGN - 1;// 分配内存p = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT);if (!p)p = vzalloc(alloc_size);if (!p)return NULL;dev = PTR_ALIGN(p, NETDEV_ALIGN);  // 网络设备的起始地址要32字节对齐dev->padded = (char *)dev - (char *)p;   // 保存网络设备的起始地址与所分配内存的起始地址之间的间距dev->pcpu_refcnt = alloc_percpu(int); // 引用计数,是PRE_CPU变量if (!dev->pcpu_refcnt)goto free_dev;if (dev_addr_init(dev))  // 插入一个空的地址到dev->dev_addrs中,并将此空地址赋给dev->dev_addr goto free_pcpu;dev_mc_init(dev); // 初始化多播地址列表为空dev_uc_init(dev); // 初始化单播地址列表为空dev_net_set(dev, &init_net);  // 当支持用户处定义网络空间时使用,略过dev->gso_max_size = GSO_MAX_SIZE;   // 65536dev->gso_max_segs = GSO_MAX_SEGS;   // 65535INIT_LIST_HEAD(&dev->napi_list);INIT_LIST_HEAD(&dev->unreg_list);INIT_LIST_HEAD(&dev->close_list);INIT_LIST_HEAD(&dev->link_watch_list);INIT_LIST_HEAD(&dev->adj_list.upper);INIT_LIST_HEAD(&dev->adj_list.lower);INIT_LIST_HEAD(&dev->all_adj_list.upper);INIT_LIST_HEAD(&dev->all_adj_list.lower);dev->priv_flags = IFF_XMIT_DST_RELEASE; // 设置标记位:在进行dev_hard_start_xmit时可以释放skb->dst/* setup为参数,以太网调用ether_setup* dev->priv_flags设置支持在传输过程中支持共享skb*/setup(dev);    dev->num_tx_queues = txqs;  // txqs = 1dev->real_num_tx_queues = txqs; // txqs = 1if (netif_alloc_netdev_queues(dev)) // 分配1个netdev_queue对象,并把它赋值给dev->_txgoto free_all;/* 是否启用sysfs虚拟文件系统。sysfs以更整齐更直观的方式向用户展示了内核的各种参数* /proc将会向sysfs迁移*/
#ifdef CONFIG_SYSFSdev->num_rx_queues = rxqs;   // txqs = 1dev->real_num_rx_queues = rxqs; // txqs = 1if (netif_alloc_rx_queues(dev)) // 分配1个netdev_rx_queue 对象,并把它赋值给dev->_rxgoto free_all;
#endifstrcpy(dev->name, name);   // 设置dev->name为传进来的参数namedev->group = INIT_NETDEV_GROUP; // 设置dev->group为0if (!dev->ethtool_ops)dev->ethtool_ops = &default_ethtool_ops;return dev;free_all:free_netdev(dev);return NULL;free_pcpu:free_percpu(dev->pcpu_refcnt);netif_free_tx_queues(dev);
#ifdef CONFIG_SYSFSkfree(dev->_rx);
#endiffree_dev:netdev_freemem(dev);return NULL;
}
EXPORT_SYMBOL(alloc_netdev_mqs);

[ drivers/net/ethernet/realtek/8139cp.c ]

// 设置缓冲的大小
static inline void cp_set_rxbufsize (struct cp_private *cp)
{unsigned int mtu = cp->dev->mtu;    // 前面设为了1500字节(ETH_DATA_LEN)if (mtu > ETH_DATA_LEN)/* MTU + ethernet header + FCS + optional VLAN tag */cp->rx_buf_sz = mtu + ETH_HLEN + 8;elsecp->rx_buf_sz = PKT_BUF_SZ;  // 设为PKT_BUF_SZ(1536字节)
}

[ drivers/net/ethernet/realtek/8139cp.c ]

// 初始化硬件寄存器
static void cp_stop_hw (struct cp_private *cp)
{cpw16(IntrStatus, ~(cpr16(IntrStatus)));   // 读取中断状态,取反,写入cpw16_f(IntrMask, 0);  // 关中断cpw8(Cmd, 0); // 命令寄存器设为0cpw16_f(CpCmd, 0);   // C+命令寄存器设为0cpw16_f(IntrStatus, ~(cpr16(IntrStatus)));    // 恢复原来的中断状态cp->rx_tail = 0;cp->tx_head = cp->tx_tail = 0;netdev_reset_queue(cp->dev);
}

[ drivers/net/ethernet/realtek/8139cp.c ]

/* Put the board into D3cold state and wait for WakeUp signal */
static void cp_set_d3_state (struct cp_private *cp)
{pci_enable_wake(cp->pdev, PCI_D0, 1); /* Enable PME# generation */pci_set_power_state (cp->pdev, PCI_D3hot); // 设置电源状态
}

初始化完成后,当开始使用时,要调用cp_open( 如当运行命令 ifconfig 设置IP地址时 ),此函数也会完成一些初始化的工作:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 当运行命令 ifconfig 设置IP地址时,会调用此函数
static int cp_open (struct net_device *dev)
{struct cp_private *cp = netdev_priv(dev);const int irq = cp->pdev->irq;    // PCI设备中断号int rc;netif_dbg(cp, ifup, dev, "enabling interface\n");rc = cp_alloc_rings(cp);  // 分配发送和接收数据的缓冲if (rc)return rc;napi_enable(&cp->napi);  // 激活NAPI(清除掉NAPI_STATE_SCHED标记)cp_init_hw(cp); // 设置设备寄存器,以便接收进来的数据直接放入缓冲中rc = request_irq(irq, cp_interrupt, IRQF_SHARED, dev->name, dev); // 注册中断,中断到来时运行cp_interrupt函数if (rc)goto err_out_hw;cp_enable_irq(cp);   // 激活中断/* 通知内核网络断开* 这里不一定是真的断开。这里只是通知内核链路层的状态改变* 一般当网络配置改变后,可通过这种方式通知内核*/netif_carrier_off(dev); mii_check_media(&cp->mii_if, netif_msg_link(cp), true);  // 检测MII接口netif_start_queue(dev);   // 告诉内核可以发送数据了return 0;err_out_hw:napi_disable(&cp->napi);cp_stop_hw(cp);cp_free_rings(cp);return rc;
}

其中调用的函数如下:
[ drivers/net/ethernet/realtek/8139cp.c ]

// 分配缓冲区
static int cp_alloc_rings (struct cp_private *cp)
{struct device *d = &cp->pdev->dev;void *mem;int rc;/* 分配内存,cp->ring_dma是物理地址,mem是对应的虚地址* 大小为 sizeof(struct cp_desc) * 64 + sizeof(struct cp_desc) * 64 + 64 * 其中64个cp_desc用作接收缓冲描述符,64个cp_desc用作发送缓冲描述符,还有64字节DMA的状态*/mem = dma_alloc_coherent(d, CP_RING_BYTES, &cp->ring_dma, GFP_KERNEL);if (!mem)return -ENOMEM;/* 设置接收缓冲描述符和发送缓冲描述符的地址,它们是在同一块内存块中*/cp->rx_ring = mem;cp->tx_ring = &cp->rx_ring[CP_RX_RING_SIZE];rc = cp_init_rings(cp);  // 对分配的内存进行初始化if (rc < 0)dma_free_coherent(d, CP_RING_BYTES, cp->rx_ring, cp->ring_dma);return rc;
}
static int cp_init_rings (struct cp_private *cp)
{
/* 接收缓冲描述符表清0
* 接收缓冲描述符表中最后一个地址设置终止标记 (1 << 30),此标记字节序为little-endian
* 这里没有对发送缓冲描述符表进行初始化,因为当发送数据时,自然会进行填充
*/
memset(cp->tx_ring, 0, sizeof(struct cp_desc) * CP_TX_RING_SIZE);
cp->tx_ring[CP_TX_RING_SIZE - 1].opts1 = cpu_to_le32(RingEnd);
cp_init_rings_index(cp);
return cp_refill_rx (cp);    // 对接收缓冲描述符表中的每个对象初始化
}
static void cp_init_rings_index (struct cp_private *cp)
{
cp->rx_tail = 0;
cp->tx_head = cp->tx_tail = 0;
}
// 对接收缓冲描述符表中的每个对象初始化
static int cp_refill_rx(struct cp_private *cp)
{
struct net_device *dev = cp->dev;
unsigned i;
for (i = 0; i < CP_RX_RING_SIZE; i++) {    // 循环对接收缓冲描述符表
struct sk_buff *skb;
dma_addr_t mapping;
// 分配一个sk_buff,其缓冲大小为1536字节,大于以太网的MTU
skb = netdev_alloc_skb_ip_align(dev, cp->rx_buf_sz);
if (!skb)
goto err_out;
/* 得到skb->data(skb对应的缓冲)的物理地址
* 从PCI设备读取数据时,要用到物理地址
*/
mapping = dma_map_single(&cp->pdev->dev, skb->data,
cp->rx_buf_sz, PCI_DMA_FROMDEVICE);
if (dma_mapping_error(&cp->pdev->dev, mapping)) {
kfree_skb(skb);
goto err_out;
}
cp->rx_skb[i] = skb;    // 接收缓冲
/* 设置cp_desc
* cp->rx_ring[i]与 cp->rx_skb[i] 一一对应
*/
cp->rx_ring[i].opts2 = 0;
cp->rx_ring[i].addr = cpu_to_le64(mapping);    // 缓冲物理地址
/* 设置cp_desc.opts1
* 缓冲的大小 | 此描述符被NIC使用
* 最后一个描述符加上一个终止标记
*/
if (i == (CP_RX_RING_SIZE - 1))    // 接收缓冲地址列表的最后一项
cp->rx_ring[i].opts1 =
cpu_to_le32(DescOwn | RingEnd | cp->rx_buf_sz);    // (1 << 31) | (1 << 30) | 1536
else
cp->rx_ring[i].opts1 =
cpu_to_le32(DescOwn | cp->rx_buf_sz);    // (1 << 31) | (1 << 30) | 1536
}
return 0;
err_out:
cp_clean_rings(cp);
return -ENOMEM;
}

[ drivers/net/ethernet/realtek/8139cp.c ]

// 设置设备寄存器,以便接收进来的数据直接放入缓冲中
static void cp_init_hw (struct cp_private *cp)
{struct net_device *dev = cp->dev;cp_reset_hw(cp);  // 对设备进行reset/* Cfg9436 EEPROM control register * Unlock ConfigX/MII register access */cpw8_f (Cfg9346, Cfg9346_Unlock);/* Restore our idea of the MAC address. * 前面从EEPROM中读入了MAC地址,现在将它们写到寄存器中*/cpw32_f (MAC0 + 0, le32_to_cpu (*(__le32 *) (dev->dev_addr + 0)));cpw32_f (MAC0 + 4, le32_to_cpu (*(__le32 *) (dev->dev_addr + 4)));/* 当硬件接收到数据,会把数据写到接收缓冲* 发送时,从发送缓冲中取数据*/cp_start_hw(cp);    /* 设置发送数据的阀值* 在硬件芯片上有一个2K字节的缓冲区,数据先放到此缓冲区,当达到一个阀值时,开始发送*/cpw8(TxThresh, 0x06); /* XXX convert magic num to a constant */__cp_set_rx_mode(dev); // 设置多播地址属性cpw32_f (TxConfig, IFG | (TX_DMA_BURST << TxDMAShift));  /* Tx configuration *//* Software marker, driver is loaded * Enable various PM features of chip */cpw8(Config1, cpr8(Config1) | DriverLoaded | PMEnable);/* Disable Wake-on-LAN. Can be turned on with ETHTOOL_SWOL */cpw8(Config3, PARMEnable);  // Enable auto-loading of PHY parms cp->wol_enabled = 0;cpw8(Config5, cpr8(Config5) & PMEStatus);   /* PME status can be reset by PCI RST# */cpw16(MultiIntr, 0);  /* Multiple interrupt select *//* Cfg9436 EEPROM control register * Lock ConfigX/MII register access */cpw8_f(Cfg9346, Cfg9346_Lock);
}
// reset硬件
static void cp_reset_hw (struct cp_private *cp)
{
unsigned work = 1000;
/* 对设置的命令寄存器写入reset命令
* 当完成reset后,此标记会自动清除
*/
cpw8(Cmd, CmdReset);
/* 循环1000次,不停读取这一标记
* 每次循环都睡眠10ms,在睡眠期间是不能被中断的。
*/
while (work--) {
if (!(cpr8(Cmd) & CmdReset))
return;
schedule_timeout_uninterruptible(10);
}
netdev_err(cp->dev, "hardware reset timeout\n");
}
/* 当硬件接收到数据,会把数据写到接收缓冲
* 发送时,从发送缓冲中取数据
*/
static inline void cp_start_hw (struct cp_private *cp)
{
dma_addr_t ring_dma;
/* 设置C+ mode 命令
* 前面被设置为:
* 如果DMA地址为64位设置 PCI Dual Address Cycle (64-bit PCI)
* Enable PCI read/write multiple
* Rx checksum offload enable
* Rx mode enable
* Tx mode enable
*/
cpw16(CpCmd, cp->cpcmd);
/*
* These (at least TxRingAddr) need to be configured after the
* corresponding bits in CpCmd are enabled. Datasheet v1.6 §6.33
* (C+ Command Register) recommends that these and more be configured
* *after* the [RT]xEnable bits in CpCmd are set. And on some hardware
* it's been observed that the TxRingAddr is actually reset to garbage
* when C+ mode Tx is enabled in CpCmd.
* 将64位发送数据缓冲的地址设为0
*/
cpw32_f(HiTxRingAddr, 0);
cpw32_f(HiTxRingAddr + 4, 0);
ring_dma = cp->ring_dma;    // 接收缓冲描述符表的物理地址
/* 接收缓冲描述符表的物理起始写入寄存器
* 当有数据到达时,数据会自动存入相应的内存地址
*/
cpw32_f(RxRingAddr, ring_dma & 0xffffffff);
cpw32_f(RxRingAddr + 4, (ring_dma >> 16) >> 16);
ring_dma += sizeof(struct cp_desc) * CP_RX_RING_SIZE;    // 发送缓冲描述符表的物理地址
/* 发送缓冲描述符表的物理起始写入寄存器
*/
cpw32_f(TxRingAddr, ring_dma & 0xffffffff);
cpw32_f(TxRingAddr + 4, (ring_dma >> 16) >> 16);
/*
* Strictly speaking, the datasheet says this should be enabled
* *before* setting the descriptor addresses. But what, then, would
* prevent it from doing DMA to random unconfigured addresses?
* This variant appears to work fine.
* 告诉设备可以接收和发送数据了
*/
cpw8(Cmd, RxOn | TxOn);
netdev_reset_queue(cp->dev);
}
/* Set or clear the multicast filter for this adaptor.
This routine is not state sensitive and need not be SMP locked.
设置多播地址属性
*/
static void __cp_set_rx_mode (struct net_device *dev)
{
struct cp_private *cp = netdev_priv(dev);
u32 mc_filter[2];    /* Multicast hash filter */
int rx_mode;
/* Note: do not reorder, GCC is clever about common statements. */
if (dev->flags & IFF_PROMISC) {    // 网络设备设置为接收所有的包
/* Unconditionally log net taps.
* Accept broadcast packets
* Accept multicast packets
* Accept pkts with our MAC as dest
* Accept all pkts w/ physical dest
*/
rx_mode =
AcceptBroadcast | AcceptMulticast | AcceptMyPhys |
AcceptAllPhys;
mc_filter[1] = mc_filter[0] = 0xffffffff;
} else if ((netdev_mc_count(dev) > multicast_filter_limit) ||    // 网络设备的多播地址数量(dev->mc)大于32
(dev->flags & IFF_ALLMULTI)) {            // 或者网络设备设置为接收所有多播地址的包
/* Too many to filter perfectly -- accept all multicasts.
* Accept broadcast packets
* Accept multicast packets
* Accept pkts with our MAC as dest
*/
rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
mc_filter[1] = mc_filter[0] = 0xffffffff;
} else {
struct netdev_hw_addr *ha;
/* Accept broadcast packets
* Accept pkts with our MAC as dest
*/
rx_mode = AcceptBroadcast | AcceptMyPhys;
mc_filter[1] = mc_filter[0] = 0;
netdev_for_each_mc_addr(ha, dev) {    // 对每一个多播地址
int bit_nr = ether_crc(ETH_ALEN, ha->addr) >> 26;
mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
rx_mode |= AcceptMulticast;    // Accept multicast packets
}
}
/* We can safely update without stopping the chip. */
cp->rx_config = cp_rx_config | rx_mode;
cpw32_f(RxConfig, cp->rx_config);  /* Rx configuration */
/* Multicast filter. */
cpw32_f (MAR0 + 0, mc_filter[0]);
cpw32_f (MAR0 + 4, mc_filter[1]);
}
// 打开中断
static void cp_enable_irq(struct cp_private *cp)
{
cpw16_f(IntrMask, cp_intr_mask);
}
// 释放缓冲空间
static void cp_free_rings (struct cp_private *cp)
{
cp_clean_rings(cp);
dma_free_coherent(&cp->pdev->dev, CP_RING_BYTES, cp->rx_ring,    // 释放缓冲描述符空间
cp->ring_dma);
cp->rx_ring = NULL;
cp->tx_ring = NULL;
}

关闭网络时,调用cp_close

[ drivers/net/ethernet/realtek/8139cp.c ]

// 关闭网络
static int cp_close (struct net_device *dev)
{struct cp_private *cp = netdev_priv(dev); // 私有数据unsigned long flags;napi_disable(&cp->napi);  // 关闭NAPInetif_dbg(cp, ifdown, dev, "disabling interface\n");spin_lock_irqsave(&cp->lock, flags);netif_stop_queue(dev);    // 告诉内核不要发送数据了/* 通知内核网络断开* 这里不一定是真的断开。这里只是通知内核链路层的状态改变* 一般当网络配置改变后,可通过这种方式通知内核*/netif_carrier_off(dev);cp_stop_hw(cp);   // 对硬件寄存器进行读写spin_unlock_irqrestore(&cp->lock, flags);free_irq(cp->pdev->irq, dev);    // 释放中断cp_free_rings(cp);   // 清除缓冲return 0;
}

发送数据时调用的函数:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 发送数据
static netdev_tx_t cp_start_xmit (struct sk_buff *skb,struct net_device *dev)
{struct cp_private *cp = netdev_priv(dev); // 私有数据unsigned entry;u32 eor, flags;unsigned long intr_flags;__le32 opts2;int mss = 0;spin_lock_irqsave(&cp->lock, intr_flags);/* This is a hard error, log it. * 发送缓冲不够*/if (TX_BUFFS_AVAIL(cp) <= (skb_shinfo(skb)->nr_frags + 1)) {netif_stop_queue(dev);spin_unlock_irqrestore(&cp->lock, intr_flags);netdev_err(dev, "BUG! Tx Ring full when queue awake!\n");return NETDEV_TX_BUSY;}entry = cp->tx_head;    // 发送缓冲开始位置eor = (entry == (CP_TX_RING_SIZE - 1)) ? RingEnd : 0; // 是否为发送缓冲最后的位置mss = skb_shinfo(skb)->gso_size; // GSO分片长度opts2 = cpu_to_le32(cp_tx_vlan_tag(skb));  /* VLAN tag */if (skb_shinfo(skb)->nr_frags == 0) {  // 没有分片的情况struct cp_desc *txd = &cp->tx_ring[entry];    // 发送描述符u32 len;dma_addr_t mapping;len = skb->len;  // 数据长度mapping = dma_map_single(&cp->pdev->dev, skb->data, len, PCI_DMA_TODEVICE);    // 得到数据的物理地址if (dma_mapping_error(&cp->pdev->dev, mapping))goto out_dma_error;txd->opts2 = opts2; // 设置描述符标记(VLAN tag)txd->addr = cpu_to_le64(mapping);   // 设置描述符物理地址wmb();  // 保证语句之前的写操作一定在语句之后的写操作之前,这是为了防止CPU为提升效率打乱写操作的顺序/* 发送缓冲最后的位置* 长度 * Descriptor is owned by NIC * First segment of a packet * Final segment of a packet */flags = eor | len | DescOwn | FirstFrag | LastFrag;if (mss)    // 支持GSOflags |= LargeSend | ((mss & MSSMask) << MSSShift);  /* TCP Large Send Offload (TSO) | GSO分片大小 */else if (skb->ip_summed == CHECKSUM_PARTIAL) { // IP局部校验const struct iphdr *ip = ip_hdr(skb); // IP头部if (ip->protocol == IPPROTO_TCP)    // 上层协议是TCPflags |= IPCS | TCPCS;  // Calculate IP checksum | Calculate TCP/IP checksum else if (ip->protocol == IPPROTO_UDP) // 上层协议是UDPflags |= IPCS | UDPCS;  // Calculate IP checksum | Calculate UDP/IP checksum elseWARN_ON(1);    /* we need a WARN() */}txd->opts1 = cpu_to_le32(flags); // 设置描述符标记(VLAN tag),注意字节序wmb(); // 保证语句之前的写操作一定在语句之后的写操作之前,这是为了防止CPU为提升效率打乱写操作的顺序cp->tx_skb[entry] = skb;    // 设置发送描述符对应的缓冲entry = NEXT_TX(entry); // 下一个描述符位置} else { // 有分片struct cp_desc *txd;u32 first_len, first_eor;dma_addr_t first_mapping;int frag, first_entry = entry; // 第一个描述符位置const struct iphdr *ip = ip_hdr(skb);   // IP头部/* We must give this initial chunk to the device last.* Otherwise we could race with the device.*/first_eor = eor;  // 第一个描述符是否为发送缓冲最后的位置first_len = skb_headlen(skb); // 主缓冲的大小(数据总大小-分片数据的大小)first_mapping = dma_map_single(&cp->pdev->dev, skb->data,    // 主缓冲数据的物理地址first_len, PCI_DMA_TODEVICE);if (dma_mapping_error(&cp->pdev->dev, first_mapping))goto out_dma_error;cp->tx_skb[entry] = skb;    // 设置发送描述符对应的缓冲entry = NEXT_TX(entry);     // 下一个描述符位置for (frag = 0; frag < skb_shinfo(skb)->nr_frags; frag++) {  // 对所有分片循环,把每一个分片都放入发送缓冲const skb_frag_t *this_frag = &skb_shinfo(skb)->frags[frag]; // 分片描述符u32 len;u32 ctrl;dma_addr_t mapping;len = skb_frag_size(this_frag);    // 分片大小mapping = dma_map_single(&cp->pdev->dev,  // 分片数据的物理地址skb_frag_address(this_frag),len, PCI_DMA_TODEVICE);if (dma_mapping_error(&cp->pdev->dev, mapping)) {unwind_tx_frag_mapping(cp, skb, first_entry, entry);goto out_dma_error;}eor = (entry == (CP_TX_RING_SIZE - 1)) ? RingEnd : 0;  // 是否为发送缓冲最后的位置/* 发送缓冲最后的位置* 长度 * Descriptor is owned by NIC */ctrl = eor | len | DescOwn;if (mss) // 支持GSOctrl |= LargeSend | ((mss & MSSMask) << MSSShift);   /* TCP Large Send Offload (TSO) | GSO分片大小 */else if (skb->ip_summed == CHECKSUM_PARTIAL) { // IP局部校验if (ip->protocol == IPPROTO_TCP)  // 上层协议是TCPctrl |= IPCS | TCPCS;   // Calculate IP checksum | Calculate TCP/IP checksum else if (ip->protocol == IPPROTO_UDP) // 上层协议是UDPctrl |= IPCS | UDPCS;   // Calculate IP checksum | Calculate UDP/IP checksum elseBUG();}if (frag == skb_shinfo(skb)->nr_frags - 1) // 最后一个分片ctrl |= LastFrag;     /* Final segment of a packet */txd = &cp->tx_ring[entry];   // 发送缓冲描述符txd->opts2 = opts2;   // 设置发送缓冲描述符标记(VLAN tag)txd->addr = cpu_to_le64(mapping); // 设置发送缓冲描述符物理地址wmb();    // 保证语句之前的写操作一定在语句之后的写操作之前,这是为了防止CPU为提升效率打乱写操作的顺序txd->opts1 = cpu_to_le32(ctrl); // 设置发送缓冲描述符标记(VLAN tag)wmb();  // 保证语句之前的写操作一定在语句之后的写操作之前,这是为了防止CPU为提升效率打乱写操作的顺序cp->tx_skb[entry] = skb;    // 设置发送描述符对应的缓冲entry = NEXT_TX(entry); // 下一个描述符位置}// fortxd = &cp->tx_ring[first_entry];  // 主缓冲对应的发送缓冲描述符txd->opts2 = opts2; // 设置发送缓冲描述符标记(VLAN tag)txd->addr = cpu_to_le64(first_mapping); // 设置发送缓冲描述符物理地址wmb();  // 保证语句之前的写操作一定在语句之后的写操作之前,这是为了防止CPU为提升效率打乱写操作的顺序if (skb->ip_summed == CHECKSUM_PARTIAL) {  // IP局部校验if (ip->protocol == IPPROTO_TCP)  // 上层协议是TCP/* 发送缓冲最后的位置* 长度 * First segment of a packet * Descriptor is owned by NIC * Calculate IP checksum * Calculate TCP/IP checksum */txd->opts1 = cpu_to_le32(first_eor | first_len |FirstFrag | DescOwn |IPCS | TCPCS);else if (ip->protocol == IPPROTO_UDP)    // 上层协议是UDP/* 发送缓冲最后的位置* 长度 * First segment of a packet * Descriptor is owned by NIC * Calculate IP checksum * Calculate UDP/IP checksum */txd->opts1 = cpu_to_le32(first_eor | first_len |FirstFrag | DescOwn |IPCS | UDPCS);elseBUG();} else/* 发送缓冲最后的位置* 长度 * First segment of a packet * Descriptor is owned by NIC */txd->opts1 = cpu_to_le32(first_eor | first_len |FirstFrag | DescOwn);wmb();   // 保证语句之前的写操作一定在语句之后的写操作之前,这是为了防止CPU为提升效率打乱写操作的顺序} // ifcp->tx_head = entry; // 当前的缓冲区头部位置netdev_sent_queue(dev, skb->len);   // 通知内核发送了多少数据netif_dbg(cp, tx_queued, cp->dev, "tx queued, slot %d, skblen %d\n",entry, skb->len);if (TX_BUFFS_AVAIL(cp) <= (MAX_SKB_FRAGS + 1))  // 有效发送缓冲少于17个,停止发送netif_stop_queue(dev);    // 发送队列设置__QUEUE_STATE_DRV_XOFF,告诉上层协议停止发送out_unlock:spin_unlock_irqrestore(&cp->lock, intr_flags);/* Tell chip to check Tx descriptors for work * One or more normal Tx packets to send */cpw8(TxPoll, NormalTxPoll);  return NETDEV_TX_OK;
out_dma_error:kfree_skb(skb);cp->dev->stats.tx_dropped++;goto out_unlock;
}

发送超时调用的函数:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 发送超时
static void cp_tx_timeout(struct net_device *dev)
{struct cp_private *cp = netdev_priv(dev); // 私有数据unsigned long flags;int rc;netdev_warn(dev, "Transmit timeout, status %2x %4x %4x %4x\n",cpr8(Cmd), cpr16(CpCmd),cpr16(IntrStatus), cpr16(IntrMask));spin_lock_irqsave(&cp->lock, flags);cp_stop_hw(cp);    // 对硬件寄存器进行读写cp_clean_rings(cp);    // 清除缓冲rc = cp_init_rings(cp); // 缓冲进行初始化/* 当硬件接收到数据,会把数据写到接收缓冲* 发送时,从发送缓冲中取数据*/cp_start_hw(cp);cp_enable_irq(cp);   // 开中断netif_wake_queue(dev);    // 通知内核可能发送数据了spin_unlock_irqrestore(&cp->lock, flags);
}
/* VLAN tag */
static inline u32 cp_tx_vlan_tag(struct sk_buff *skb)
{
return vlan_tx_tag_present(skb) ?
TxVlanTag | swab16(vlan_tx_tag_get(skb)) : 0x00;
}
// 将分片的物理地址取消映射
static void unwind_tx_frag_mapping(struct cp_private *cp, struct sk_buff *skb,
int first, int entry_last)
{
int frag, index;
struct cp_desc *txd;
skb_frag_t *this_frag;
for (frag = 0; frag+first < entry_last; frag++) {    // 对所有分片循环
index = first+frag;
cp->tx_skb[index] = NULL;
txd = &cp->tx_ring[index];    // 对应的发送缓冲描述符
this_frag = &skb_shinfo(skb)->frags[frag];
dma_unmap_single(&cp->pdev->dev, le64_to_cpu(txd->addr),    // 取消映射
skb_frag_size(this_frag), PCI_DMA_TODEVICE);
}
}

接收数据,当有数据到达时,会产生中断,在中断函数处理接收数据:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 中断函数
static irqreturn_t cp_interrupt (int irq, void *dev_instance)
{struct net_device *dev = dev_instance;struct cp_private *cp;int handled = 0;u16 status;if (unlikely(dev == NULL))return IRQ_NONE;cp = netdev_priv(dev);   // 私有数据spin_lock(&cp->lock);status = cpr16(IntrStatus); // 得到中断状态if (!status || (status == 0xFFFF))goto out_unlock;handled = 1;netif_dbg(cp, intr, dev, "intr, status %04x cmd %02x cpcmd %04x\n",status, cpr8(Cmd), cpr16(CpCmd));/* Rx error * No Rx descriptors available * Rx FIFO Overflow * Rx packet received * 将上面的标记清除*/cpw16(IntrStatus, status & ~cp_rx_intr_mask);/* close possible race's with dev_close */if (unlikely(!netif_running(dev))) {cpw16(IntrMask, 0);goto out_unlock;}/* Rx packet received * Rx error * No Rx descriptors available * Rx FIFO Overflow * 接收到了数据*/if (status & (RxOK | RxErr | RxEmpty | RxFIFOOvr))if (napi_schedule_prep(&cp->napi)) { // 如果支持NAPI且NAPI可以运行cpw16_f(IntrMask, cp_norx_intr_mask);   // 关闭接收中断,保留发送中断/* 将cp->napi加入到softnet_data中(softnet_data是一个PRE_CPU变量)* 激活软中断,在软中断注册的轮询函数(cp->napi己绑定函数cp_rx_poll)中完成网络数据包的接收操作*/__napi_schedule(&cp->napi);  }/* Tx packet sent * Tx error * No Tx descriptors available * Software-requested interrupt * 为上面状态时,说时传输完成或出错,调用函数cp_tx进行一些清理工作*/if (status & (TxOK | TxErr | TxEmpty | SWInt))cp_tx(cp);/* Packet underrun, or link change * 当网线拔插时会产生LinkChg中断*/if (status & LinkChg)mii_check_media(&cp->mii_if, netif_msg_link(cp), false);    // 检测MII接口if (status & PciErr) {    // PCI 设备出错u16 pci_status;pci_read_config_word(cp->pdev, PCI_STATUS, &pci_status);pci_write_config_word(cp->pdev, PCI_STATUS, pci_status);netdev_err(dev, "PCI bus error, status=%04x, PCI status=%04x\n",status, pci_status);/* TODO: reset hardware */}out_unlock:spin_unlock(&cp->lock);return IRQ_RETVAL(handled);
}

在中断函数中调用的函数:

[ drivers/net/ethernet/realtek/8139cp.c ]

// 传输完成后的清理工作
static void cp_tx (struct cp_private *cp)
{unsigned tx_head = cp->tx_head;    // 发送缓冲描述符表的开头位置unsigned tx_tail = cp->tx_tail; // 发送缓冲描述符表的结束位置unsigned bytes_compl = 0, pkts_compl = 0;while (tx_tail != tx_head) {struct cp_desc *txd = cp->tx_ring + tx_tail;   // 从发送缓冲描述符表的末尾开始struct sk_buff *skb;u32 status;rmb();  // 保证此语句前面和后面语句的执行顺序(禁止编译器进行优化)status = le32_to_cpu(txd->opts1);if (status & DescOwn) /* Descriptor is owned by NIC */break;skb = cp->tx_skb[tx_tail];    // 得到对应的缓冲BUG_ON(!skb);// 取消描述符的地址映射dma_unmap_single(&cp->pdev->dev, le64_to_cpu(txd->addr),   // 内存物理地址le32_to_cpu(txd->opts1) & 0xffff,   // 内存大小PCI_DMA_TODEVICE);if (status & LastFrag) {   /* Final segment of a packet *//* Tx error summary * Tx FIFO underrun * 出错,统计出错数量*/if (status & (TxError | TxFIFOUnder)) {netif_dbg(cp, tx_err, cp->dev,"tx err, status 0x%x\n", status);cp->dev->stats.tx_errors++;    // 发送错误总数if (status & TxOWC)cp->dev->stats.tx_window_errors++;  /* Tx Out-of-window collision */if (status & TxMaxCol)cp->dev->stats.tx_aborted_errors++;   /* Tx aborted due to excessive collisions */if (status & TxLinkFail)cp->dev->stats.tx_carrier_errors++;     /* Link failed during Tx of packet */if (status & TxFIFOUnder)cp->dev->stats.tx_fifo_errors++;  /* Tx FIFO underrun */} else {cp->dev->stats.collisions +=((status >> TxColCntShift) & TxColCntMask); // 冲突数量cp->dev->stats.tx_packets++; // 发送总数量cp->dev->stats.tx_bytes += skb->len; // 发送总大小netif_dbg(cp, tx_done, cp->dev,"tx done, slot %d\n", tx_tail);}bytes_compl += skb->len;   // 所有缓冲中的数据大小pkts_compl++;    // 缓冲的数量dev_kfree_skb_irq(skb); // 释放缓冲}cp->tx_skb[tx_tail] = NULL; // 缓冲设为空tx_tail = NEXT_TX(tx_tail);    // 下一个描述符}cp->tx_tail = tx_tail;    // 记录当前的描述符尾部netdev_completed_queue(cp->dev, pkts_compl, bytes_compl);   // 通知内核发送的数据包个数和数据大小/* 如果可用描述符空间大于 16 + 1,重新开始传输*/if (TX_BUFFS_AVAIL(cp) > (MAX_SKB_FRAGS + 1))    netif_wake_queue(cp->dev);   // 通知内核可能发送数据了
}

在接收数据时为提高性能可使用NAPI。原理就是当硬件中断产生后,通过一个轮询函数(通过软中断实现)不停读取数据,在这个过程中网卡的中断被关闭。其中的轮询函数在初始化时被指定为cp_rx_poll

[ drivers/net/ethernet/realtek/8139cp.c ]

// NAPI时进行读取数据的操作
static int cp_rx_poll(struct napi_struct *napi, int budget)
{struct cp_private *cp = container_of(napi, struct cp_private, napi);  // 私有数据struct net_device *dev = cp->dev;    // 网络设备unsigned int rx_tail = cp->rx_tail;  // 读取描述符的尾部int rx;  // 接收到的包的数量rx_status_loop:rx = 0;/* 中断状态为* Rx packet received * Rx error * No Rx descriptors available * Rx FIFO Overflow */cpw16(IntrStatus, cp_rx_intr_mask);/* 如果有数据到达,会自动放入缓冲中* 下面就是从缓冲中读取数据*/while (1) {u32 status, len;dma_addr_t mapping, new_mapping;struct sk_buff *skb, *new_skb;struct cp_desc *desc;const unsigned buflen = cp->rx_buf_sz;  // 1536字节skb = cp->rx_skb[rx_tail]; // 得到缓冲BUG_ON(!skb);desc = &cp->rx_ring[rx_tail];   // 得到描述符status = le32_to_cpu(desc->opts1);  // 描述符状态if (status & DescOwn)   /* Descriptor is owned by NIC */break;len = (status & 0x1fff) - 4; // 缓冲中的数据长度mapping = le64_to_cpu(desc->addr);   // 缓冲对应的物理地址/* First segment of a packet * Final segment of a packet * 这两种情况记录一些出错信息然后直接丢弃*/if ((status & (FirstFrag | LastFrag)) != (FirstFrag | LastFrag)) {/* we don't support incoming fragmented frames.* instead, we attempt to ensure that the* pre-allocated RX skbs are properly sized such* that RX fragments are never encountered*/cp_rx_err_acct(cp, rx_tail, status, len);dev->stats.rx_dropped++;cp->cp_stats.rx_frags++;goto rx_next;}/* Rx error summary * Rx error, FIFO overflowed, pkt bad * 这两种情况记录一些出错信息然后直接丢弃*/if (status & (RxError | RxErrFIFO)) {cp_rx_err_acct(cp, rx_tail, status, len);goto rx_next;}netif_dbg(cp, rx_status, dev, "rx slot %d status 0x%x len %d\n",rx_tail, status, len);/* 分配一个新的内存缓冲块* 原来的缓冲要从缓冲列表中摘除向上层传递* 这个新的内存用来替换原来的空间*/new_skb = netdev_alloc_skb_ip_align(dev, buflen);if (!new_skb) {dev->stats.rx_dropped++;goto rx_next;}/* 得到new_skb->data(new_skb对应的缓冲)的物理地址* 从PCI设备读取数据时,要用到物理地址*/new_mapping = dma_map_single(&cp->pdev->dev, new_skb->data, buflen,PCI_DMA_FROMDEVICE);if (dma_mapping_error(&cp->pdev->dev, new_mapping)) {dev->stats.rx_dropped++;kfree_skb(new_skb);goto rx_next;}// 取消原描述符的地址映射dma_unmap_single(&cp->pdev->dev, mapping,buflen, PCI_DMA_FROMDEVICE);/* Handle checksum offloading for incoming packets. */if (cp_rx_csum_ok(status))skb->ip_summed = CHECKSUM_UNNECESSARY;elseskb_checksum_none_assert(skb);skb_put(skb, len); // 调整skb的数据大小cp->rx_skb[rx_tail] = new_skb; // 将原缓冲(skb)的位置用新的替换cp_rx_skb(cp, skb, desc);   // 将skb交给上层协议rx++;    // 接收到的包的数量mapping = new_mapping;  // 新缓冲的物理地址rx_next:/* 设置新缓冲的属性*/cp->rx_ring[rx_tail].opts2 = 0;cp->rx_ring[rx_tail].addr = cpu_to_le64(mapping);    // 缓冲物理地址/* 设置cp_desc.opts1* 缓冲的大小 | 此描述符被NIC使用 * 最后一个描述符加上一个终止标记*/if (rx_tail == (CP_RX_RING_SIZE - 1))  // 接收缓冲地址列表的最后一项desc->opts1 = cpu_to_le32(DescOwn | RingEnd | cp->rx_buf_sz);    // (1 << 31) | (1 << 30) | 1536elsedesc->opts1 = cpu_to_le32(DescOwn | cp->rx_buf_sz);   // (1 << 31) | (1 << 30) | 1536rx_tail = NEXT_RX(rx_tail); // 下一个描述符if (rx >= budget)  // 如果接收的数据包超过指定的值(参数),结束此次读取break;} // whilecp->rx_tail = rx_tail; // 当前描述符表的末尾/* if we did not reach work limit, then we're done with* this round of polling*/if (rx < budget) {unsigned long flags;/* Rx packet received * Rx error * No Rx descriptors available * Rx FIFO Overflow * 如果中断状态为上面的值,回到函数开始处*/if (cpr16(IntrStatus) & cp_rx_intr_mask)goto rx_status_loop;napi_gro_flush(napi, false);spin_lock_irqsave(&cp->lock, flags);__napi_complete(napi);cpw16_f(IntrMask, cp_intr_mask); // 打开接收和发送中断spin_unlock_irqrestore(&cp->lock, flags);}return rx;
}

在轮询函数中调用的函数:
[ drivers/net/ethernet/realtek/8139cp.c ]

// 接收时的错误统计
static void cp_rx_err_acct (struct cp_private *cp, unsigned rx_tail,u32 status, u32 len)
{netif_dbg(cp, rx_err, cp->dev, "rx err, slot %d status 0x%x len %d\n",rx_tail, status, len);cp->dev->stats.rx_errors++;   // 总的错误数if (status & RxErrFrame)cp->dev->stats.rx_frame_errors++;  /* Rx frame alignment error */if (status & RxErrCRC)cp->dev->stats.rx_crc_errors++;  /* Rx CRC error */if ((status & RxErrRunt) || (status & RxErrLong))  cp->dev->stats.rx_length_errors++; /* Rx error, packet > 4096 bytes or packet < 64 bytes */if ((status & (FirstFrag | LastFrag)) != (FirstFrag | LastFrag))  /* First segment of a packet */ /* Final segment of a packet */cp->dev->stats.rx_length_errors++;if (status & RxErrFIFO)cp->dev->stats.rx_fifo_errors++;  /* Rx error, FIFO overflowed, pkt bad */
}
// 检验和是否正确
static inline unsigned int cp_rx_csum_ok (u32 status)
{
unsigned int protocol = (status >> 16) & 0x3;
/* 协议是TCP,TCP的检验和无错或协议是UDP且UDP的检验和无错
*/
if (((protocol == RxProtoTCP) && !(status & TCPFail)) ||
((protocol == RxProtoUDP) && !(status & UDPFail)))
return 1;
else
return 0;
}
// 将skb交给上层协议
static inline void cp_rx_skb (struct cp_private *cp, struct sk_buff *skb,
struct cp_desc *desc)
{
u32 opts2 = le32_to_cpu(desc->opts2);    /* Rx VLAN tag available */
/* skb->data指向了以太网头部后面,也就是IP头部的位置
* 返回数据包的以太网协议
*/
skb->protocol = eth_type_trans (skb, cp->dev);
cp->dev->stats.rx_packets++;    // 收到的包的数量
cp->dev->stats.rx_bytes += skb->len;    // 收到的包的大小
if (opts2 & RxVlanTagged)  /* Rx VLAN tag available */
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), swab16(opts2 & 0xffff)); /* 802.1Q VLAN Extended Header  */
napi_gro_receive(&cp->napi, skb);    // GRO机制,将小的数据包尽可能合并成大的数据包,然后提交到上层协议
}

当支持电源管理时,挂起和继续时调用的函数:

[ drivers/net/ethernet/realtek/8139cp.c ]

#ifdef CONFIG_PM
static int cp_suspend (struct pci_dev *pdev, pm_message_t state)
{struct net_device *dev = pci_get_drvdata(pdev);   // 网络设备struct cp_private *cp = netdev_priv(dev);   // 私有数据unsigned long flags;if (!netif_running(dev)) // 设备是否被唤醒(dev->state 是否设了__LINK_STATE_START标记)return 0;netif_device_detach (dev);   // 清除设备状态(dev->state)的__LINK_STATE_PRESENT标记,禁止发送队列netif_stop_queue (dev);    // 告诉内核不要发送数据了spin_lock_irqsave (&cp->lock, flags);/* Disable Rx and Tx */cpw16 (IntrMask, 0);   // 关中断cpw8  (Cmd, cpr8 (Cmd) & (~RxOn | ~TxOn));    // 禁止发送和接收数据spin_unlock_irqrestore (&cp->lock, flags);pci_save_state(pdev);  // 保存PCI状态pci_enable_wake(pdev, pci_choose_state(pdev, state), cp->wol_enabled); // 根据是否支持Wake-on-LAN禁止或允许PME#pci_set_power_state(pdev, pci_choose_state(pdev, state));   // 设置电源状态return 0;
}// 继续运行
static int cp_resume (struct pci_dev *pdev)
{struct net_device *dev = pci_get_drvdata (pdev);  // 网络设备struct cp_private *cp = netdev_priv(dev);   // 私有数据unsigned long flags;if (!netif_running(dev)) // 设备是否被唤醒(dev->state 是否设了__LINK_STATE_START标记)return 0;netif_device_attach (dev);   // 设置设备状态(dev->state)的__LINK_STATE_PRESENT标记,并唤醒发送队列pci_set_power_state(pdev, PCI_D0);    // 设置电源状态为PCI_D0    pci_restore_state(pdev);    // 恢复PCI设备保存的状态pci_enable_wake(pdev, PCI_D0, 0);    // 禁止PME#的产生/* FIXME: sh*t may happen if the Rx ring buffer is depleted */cp_init_rings_index (cp);cp_init_hw (cp);     // 设置设备寄存器,以便接收进来的数据直接放入缓冲中cp_enable_irq(cp);    // 打开中断netif_start_queue (dev); // 告诉内核可以发送数据了spin_lock_irqsave (&cp->lock, flags);mii_check_media(&cp->mii_if, netif_msg_link(cp), false);   // 检测MII接口spin_unlock_irqrestore (&cp->lock, flags);return 0;
}
#endif /* CONFIG_PM */

其它的一些函数

[ drivers/net/ethernet/realtek/8139cp.c ]

#ifdef CONFIG_NET_POLL_CONTROLLER
/** Polling receive - used by netconsole and other diagnostic tools* to allow network i/o with interrupts disabled.*/
static void cp_poll_controller(struct net_device *dev)
{struct cp_private *cp = netdev_priv(dev); // 私有数据const int irq = cp->pdev->irq;    // 中断号disable_irq(irq); // 关闭中断cp_interrupt(irq, dev);  // 调用中断函数enable_irq(irq);   // 打开中断
}
#endif

[ drivers/net/ethernet/realtek/8139cp.c ]

// 设置多播地址属性
static void cp_set_rx_mode (struct net_device *dev)
{unsigned long flags;struct cp_private *cp = netdev_priv(dev); // 私有数据spin_lock_irqsave (&cp->lock, flags);__cp_set_rx_mode(dev);spin_unlock_irqrestore (&cp->lock, flags);
}

[ drivers/net/ethernet/realtek/8139cp.c ]

// 这里只是得到接收被忽略的数据个数
static void __cp_get_stats(struct cp_private *cp)
{/* only lower 24 bits valid; write any value to clear */cp->dev->stats.rx_missed_errors += (cpr32 (RxMissed) & 0xffffff);cpw32 (RxMissed, 0);  /* 24 bits valid, write clears */
}// 这里只是得到接收被忽略的数据个数
static struct net_device_stats *cp_get_stats(struct net_device *dev)
{struct cp_private *cp = netdev_priv(dev);unsigned long flags;/* The chip only need report frame silently dropped. */spin_lock_irqsave(&cp->lock, flags);if (netif_running(dev) && netif_device_present(dev))__cp_get_stats(cp);spin_unlock_irqrestore(&cp->lock, flags);return &dev->stats;
}

[ drivers/net/ethernet/realtek/8139cp.c ]

// 改变MTU
static int cp_change_mtu(struct net_device *dev, int new_mtu)
{struct cp_private *cp = netdev_priv(dev); // 私有数据/* check for invalid MTU, according to hardware limits * 60 - 4096*/if (new_mtu < CP_MIN_MTU || new_mtu > CP_MAX_MTU)return -EINVAL;/* if network interface not up, no need for complexity */if (!netif_running(dev)) {dev->mtu = new_mtu;cp_set_rxbufsize(cp);    /* set new rx buf size */return 0;}/* network IS up, close it, reset MTU, and come up again. */cp_close(dev);dev->mtu = new_mtu;cp_set_rxbufsize(cp);return cp_open(dev);
}

[ drivers/net/ethernet/realtek/8139cp.c ]

/* Set the ethtool Wake-on-LAN settings */
static int netdev_set_wol (struct cp_private *cp,const struct ethtool_wolinfo *wol)
{u8 options;/* Wake up when the cable connection is re-established * Wake up when receives a Magic Packet */options = cpr8 (Config3) & ~(LinkUp | MagicPacket);/* If WOL is being disabled, no need for complexity */if (wol->wolopts) {if (wol->wolopts & WAKE_PHY) options |= LinkUp;     // Wake up when the cable connection is re-established if (wol->wolopts & WAKE_MAGIC)    options |= MagicPacket; // Wake up when receives a Magic Packet }cpw8 (Cfg9346, Cfg9346_Unlock);cpw8 (Config3, options);   // 写到Config3的位置cpw8 (Cfg9346, Cfg9346_Lock);options = 0; /* Paranoia setting *//* Accept Unicast wakeup frame * Accept Multicast wakeup frame * Accept Broadcast wakeup frame * 先去掉以上标记*/options = cpr8 (Config5) & ~(UWF | MWF | BWF);/* If WOL is being disabled, no need for complexity */if (wol->wolopts) {if (wol->wolopts & WAKE_UCAST)  options |= UWF;    // Accept Unicast wakeup frame if (wol->wolopts & WAKE_BCAST)    options |= BWF;    // Accept Broadcast wakeup frame if (wol->wolopts & WAKE_MCAST)  options |= MWF;    // Accept Multicast wakeup frame }cpw8 (Config5, options);cp->wol_enabled = (wol->wolopts) ? 1 : 0;return 0;
}
/* Get the ethtool Wake-on-LAN settings */
static void netdev_get_wol (struct cp_private *cp,
struct ethtool_wolinfo *wol)
{
u8 options;
wol->wolopts   = 0; /* Start from scratch */
wol->supported = WAKE_PHY   | WAKE_BCAST | WAKE_MAGIC |
WAKE_MCAST | WAKE_UCAST;
/* We don't need to go on if WOL is disabled */
if (!cp->wol_enabled) return;
options        = cpr8 (Config3);
if (options & LinkUp)        wol->wolopts |= WAKE_PHY;    // Wake up when the cable connection is re-established
if (options & MagicPacket)   wol->wolopts |= WAKE_MAGIC;    // Wake up when receives a Magic Packet
options        = 0; /* Paranoia setting */
options        = cpr8 (Config5);
if (options & UWF)           wol->wolopts |= WAKE_UCAST;    // Accept Unicast wakeup frame
if (options & BWF)           wol->wolopts |= WAKE_BCAST;    // Accept Broadcast wakeup frame
if (options & MWF)           wol->wolopts |= WAKE_MCAST;    // Accept Multicast wakeup frame
}

[ drivers/net/ethernet/realtek/8139cp.c ]

// 驱动信息
static void cp_get_drvinfo (struct net_device *dev, struct ethtool_drvinfo *info)
{struct cp_private *cp = netdev_priv(dev); // 私有数据strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); // "8139cp"strlcpy(info->version, DRV_VERSION, sizeof(info->version));  // "1.3"strlcpy(info->bus_info, pci_name(cp->pdev), sizeof(info->bus_info));
}static void cp_get_ringparam(struct net_device *dev,struct ethtool_ringparam *ring)
{ring->rx_max_pending = CP_RX_RING_SIZE;    // 64ring->tx_max_pending = CP_TX_RING_SIZE;    // 64ring->rx_pending = CP_RX_RING_SIZE;    // 64ring->tx_pending = CP_TX_RING_SIZE;    // 64
}static int cp_get_regs_len(struct net_device *dev)
{return CP_REGS_SIZE;   // (0xff + 1)
}static int cp_get_sset_count (struct net_device *dev, int sset)
{switch (sset) {case ETH_SS_STATS:return CP_NUM_STATS;  // 14default:return -EOPNOTSUPP;}
}

[ drivers/net/ethernet/realtek/8139cp.c ]

// 设置MAC地址
static int cp_set_mac_address(struct net_device *dev, void *p)
{struct cp_private *cp = netdev_priv(dev);// 私有数据struct sockaddr *addr = p;/* 地址不是00:00:00:00:00:00, 也不是FF:FF:FF:FF:FF:FF*/if (!is_valid_ether_addr(addr->sa_data))return -EADDRNOTAVAIL;memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); // 拷贝地址spin_lock_irq(&cp->lock);cpw8_f(Cfg9346, Cfg9346_Unlock);cpw32_f(MAC0 + 0, le32_to_cpu (*(__le32 *) (dev->dev_addr + 0)));   // 写到寄存器中cpw32_f(MAC0 + 4, le32_to_cpu (*(__le32 *) (dev->dev_addr + 4)));cpw8_f(Cfg9346, Cfg9346_Lock);spin_unlock_irq(&cp->lock);return 0;
}

网卡驱动:8139Cp相关推荐

  1. Linux服务器网卡驱动安装及故障排除(转)

    Linux服务器网卡驱动安装及故障排除(转) 转自:http://www.ccw.com.cn/server/yyjq/htm2005/20050817_15OF4.htm感谢原创作者 曹江华 Lin ...

  2. Linux下网卡驱动安装及故障排除

    Linux下网卡驱动安装及故障排除 赛迪网 2007-4-26 9:10:00文/forgiven 网卡是Linux服务器中最重要网络设备.据统计,Linux网络故障有35%在物理层.25%在数据链路 ...

  3. linux 查看网卡损坏,Linux服务器网卡驱动安装及故障排除

    Linux服务器网卡驱动安装及故障排除 网卡是Linux服务器中最重要网络设备.据统计,Linux网络故障有35%在物理层.25%在数据链路层.10%在网络层.10%在传输层.10%在对话.7%在表示 ...

  4. 无法安装此计算机不存在英特尔,win2008serverr2intel网卡驱动无法安装不存在英特尔PRO适配器的解决方法...

    无法安装驱动程序,此计算机上不存在英特尔(R),PRO适配器,甚至去官网下载了 intel 82579vforwindows 2008 r2 的驱动 都提示这个. 解决方法: intel官网下载for ...

  5. 备份一个万能网卡驱动

    昨天安装了一个windows2008,因为是在pc上安装,还是个老机器,安装完成后竟然发现我的网卡驱动未成功安装,我就觉得弄个驱动精灵不就ok了吗,结果安装提示不支持server版本,安装驱动人生也不 ...

  6. linux拓实n87驱动下载,拓实n87网卡驱动for xp/win7官方版

    拓实n87网卡驱动for xp/win7官方版是一个十分强大的网卡驱动管理软件,拓实n87网卡驱动for xp/win7官方版是ts N87高增益全向无线USB网卡驱动程序,拓实n87全面支持移动cm ...

  7. (超贴心)Centos7安装2.5G网卡驱动(Realtek 3000)

    文章目录 前言 准备 正题 问题1: 问题2 问题3 问题4 前言 首先跟大家聊聊我的情况吧.因为网络极其关键,要不然服务器还服务个毛,就会是一个废物. 我是在最小化安装Centos7系统的时候,设置 ...

  8. 【技术贴】虚拟机 VMware win7 win8网卡驱动下载 解决虚拟机不识别网卡没有本地连接...

    解决虚拟机VMware7.0下虚拟win7 win8找不到网卡,不能识别网卡.没有本地连接.(本篇文章只适合虚拟机win7/win8 32位环境) 废话不多说,直接入题.vmware 虚拟机 win7 ...

  9. Linux 网卡驱动相关——03

    紧接上一篇,这里简要介绍net_device 结构和网卡驱动框架. struct net_device  是一个比sk_buff 更复杂的结构,里面包含了与TCP/IP协议栈通信的接口函数,但是自从2 ...

最新文章

  1. Problem A: 平面上的点——Point类 (I)
  2. 库克工资是普通员工201倍,纳德拉年薪三个亿,大公司贫富差距榜出炉
  3. Windows服务的程序方面的资料
  4. 5岁自学python编程-枣庄适合小学生学的少儿编程课程在哪里
  5. 耳机使用说明书 jbl ua_怎么挑选一款适合自己的蓝牙耳机?看看这篇文章!
  6. PHP自定义数组转Json字符串函数
  7. 操作系统如何恢复到原先状态
  8. C++串口交互数据监听方法与虚拟串口工具安装
  9. android 模拟器 超时,Appcelerator Studio超时等待Android模拟器启动
  10. 信息安全管理(CISP)—— 网络安全监管
  11. 免费在线语音识别成文字
  12. 如何制作macOS Big Sur 系统启动U盘
  13. python xpath定位 麦客表单
  14. 《程序员》2013年4期精彩内容:中国云计算大势图
  15. C++简介 C语言编程原理
  16. java-php-python-晨光文具店进销存系统设计与开发计算机毕业设计
  17. Unparseable date
  18. 面试题目:2个鸡蛋100层楼问题
  19. Firebox比较常用的插件
  20. 如何设计一款优秀的短视频 SDK

热门文章

  1. csgo躲猫猫模式显示服务器已满,CSGO有躲猫猫模式吗 CSGO躲猫猫模式进入方法一览...
  2. 【开发记录】利用QT读取Excel并写入数据
  3. python车辆管理系统_python实现停车管理系统
  4. 如何增加产品属性(鞋码/衣服尺寸)
  5. Python数据类型、列表、元组、字典、集合增删改查(切片)
  6. java前端需要学什么_Web前端需要学什么?工作内容是什么?
  7. 原生html如何发送网络请求,原生JS发送HTTP请求的方式:XMLHttpRequest.send()
  8. SSM发送手机验证码——以网建SMS为例
  9. 科笛集团冲刺港股:上半年亏2.5亿 红杉与云锋基金是股东
  10. 冬天练习太极拳的老人