1、网卡的初始化
e1000网卡的初始化调用函数e1000_probe(),网卡作为一种PCI设备就要初始化PCI设备的一些属性,设置DMA,初始化驱动的操作函数(收包函数、发包函数)、设置循环队列缓冲区大小,把网卡注册到dev_base全局数组中。网卡的收包方式有三种
(1)中断方式:
传统的网卡都采用这种模式,因为传统的网卡收包量不是很大,这种方式就是当有一个数据包来了就产生一个中断,然后cpu就放下手中的事情去把网络数据包拷贝到内存处理,当流量很大时会产生大量的中断,cpu就不断的上下文切换,这样是很损耗cpu的性能,这种只适合网络流量不大的情况。
(2)poll
第二种方式是轮询,设置一个定时器,每隔一定时间cpu就去查看是网卡是否有接受数据包,如果有数据上来就拷贝内存处理,这种方式适合大流量情景,避免cpu产生大量中断。但当网络流量小时,cpu还有不断的去检查网卡就损耗了cpu资源。
(3)DMA
系统会在内存中开辟一个缓冲区环形队列来收发数据包,然后把缓冲区通过DMA映射到网卡,这样网卡收到数据包后就是直接是放到内存的环形队列中,然后产生中断让cpu去处理,这样cpu可以自己干自己的事情,只有当有数据包到来才去内存中处理。
(4)e1000_probe函数
e1000_probe函数主要的工作是初始化硬件、注册初始化新设备,直接上代码
static int __devinit e1000_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct net_device *netdev;
struct e1000_adapter *adapter;
struct e1000_hw *hw;
const struct e1000_info *ei = e1000_info_tbl[ent->driver_data];
resource_size_t mmio_start, mmio_len;
resource_size_t flash_start, flash_len;
static int cards_found;
int i, err, pci_using_dac;
u16 eeprom_data = 0;
u16 eeprom_apme_mask = E1000_EEPROM_APME;
if (ei->flags2 & FLAG2_DISABLE_ASPM_L1)
e1000e_disable_aspm(pdev, PCIE_LINK_STATE_L1);
err = pci_enable_device_mem(pdev);
if (err)
return err;
pci_using_dac = 0;
//设置pci设备的dma掩码
err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
if (!err) {
err = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
if (!err)
pci_using_dac = 1;
} else {
err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
if (err) {
err = dma_set_coherent_mask(&pdev->dev,
DMA_BIT_MASK(32));
if (err) {
dev_err(&pdev->dev, "No usable DMA "
"configuration, aborting\n");
goto err_dma;
}
}
}
err = pci_request_selected_regions_exclusive(pdev,
pci_select_bars(pdev, IORESOURCE_MEM),
e1000e_driver_name);
if (err)
goto err_pci_reg;
/* AER (Advanced Error Reporting) hooks */
pci_enable_pcie_error_reporting(pdev);
pci_set_master(pdev);
/* PCI config space info */
err = pci_save_state(pdev);
if (err)
goto err_alloc_etherdev;
err = -ENOMEM;
//为e1000网卡对应的net_device结构分配内存
netdev = alloc_etherdev(sizeof(struct e1000_adapter));
if (!netdev)
goto err_alloc_etherdev;
SET_NETDEV_DEV(netdev, &pdev->dev);
netdev->irq = pdev->irq;
pci_set_drvdata(pdev, netdev);
adapter = netdev_priv(netdev);
hw = &adapter->hw;
adapter->netdev = netdev;
adapter->pdev = pdev;
adapter->ei = ei;
adapter->pba = ei->pba;
adapter->flags = ei->flags;
adapter->flags2 = ei->flags2;
adapter->hw.adapter = adapter;
adapter->hw.mac.type = ei->mac;
adapter->max_hw_frame_size = ei->max_hw_frame_size;
adapter->msg_enable = (1 << NETIF_MSG_DRV | NETIF_MSG_PROBE) - 1;
mmio_start = pci_resource_start(pdev, 0);
mmio_len = pci_resource_len(pdev, 0);
err = -EIO;
adapter->hw.hw_addr = ioremap(mmio_start, mmio_len);
if (!adapter->hw.hw_addr)
goto err_ioremap;
if ((adapter->flags & FLAG_HAS_FLASH) &&
(pci_resource_flags(pdev, 1) & IORESOURCE_MEM)) {
flash_start = pci_resource_start(pdev, 1);
flash_len = pci_resource_len(pdev, 1);
adapter->hw.flash_address = ioremap(flash_start, flash_len);
if (!adapter->hw.flash_address)
goto err_flashmap;
}
/* construct the net_device struct 驱动的发包收包等处理函数*/
netdev->netdev_ops = &e1000e_netdev_ops;
//网卡命令函数注册
e1000e_set_ethtool_ops(netdev);
netdev->watchdog_timeo = 5 * HZ;
// 注册poll函数为e1000_clean, weight为64
netif_napi_add(netdev, &adapter->napi, e1000_clean, 64);
strncpy(netdev->name, pci_name(pdev), sizeof(netdev->name) - 1);
netdev->mem_start = mmio_start;
netdev->mem_end = mmio_start + mmio_len;
adapter->bd_number = cards_found++;
e1000e_check_options(adapter);
/* setup adapter struct */
err = e1000_sw_init(adapter);
if (err)
goto err_sw_init;
err = -EIO;
memcpy(&hw->mac.ops, ei->mac_ops, sizeof(hw->mac.ops));
memcpy(&hw->nvm.ops, ei->nvm_ops, sizeof(hw->nvm.ops));
memcpy(&hw->phy.ops, ei->phy_ops, sizeof(hw->phy.ops));
err = ei->get_variants(adapter);
if (err)
goto err_hw_init;
if ((adapter->flags & FLAG_IS_ICH) &&
(adapter->flags & FLAG_READ_ONLY_NVM))
e1000e_write_protect_nvm_ich8lan(&adapter->hw);
hw->mac.ops.get_bus_info(&adapter->hw);
adapter->hw.phy.autoneg_wait_to_complete = 0;
/* Copper options */
if (adapter->hw.phy.media_type == e1000_media_type_copper) {
adapter->hw.phy.mdix = AUTO_ALL_MODES;
adapter->hw.phy.disable_polarity_correction = 0;
adapter->hw.phy.ms_type = e1000_ms_hw_default;
}
if (e1000_check_reset_block(&adapter->hw))
e_info("PHY reset is blocked due to SOL/IDER session.\n");
netdev->features = NETIF_F_SG |
NETIF_F_HW_CSUM |
NETIF_F_HW_VLAN_TX |
NETIF_F_HW_VLAN_RX;
if (adapter->flags & FLAG_HAS_HW_VLAN_FILTER)
netdev->features |= NETIF_F_HW_VLAN_FILTER;
//网卡属性初始化
netdev->features |= NETIF_F_TSO;
netdev->features |= NETIF_F_TSO6;
netdev->vlan_features |= NETIF_F_TSO;
netdev->vlan_features |= NETIF_F_TSO6;
netdev->vlan_features |= NETIF_F_HW_CSUM;
netdev->vlan_features |= NETIF_F_SG;
if (pci_using_dac)
netdev->features |= NETIF_F_HIGHDMA;
if (e1000e_enable_mng_pass_thru(&adapter->hw))
adapter->flags |= FLAG_MNG_PT_ENABLED;
/*
* before reading the NVM, reset the controller to
* put the device in a known good starting state
*/
adapter->hw.mac.ops.reset_hw(&adapter->hw);
/*
* systems with ASPM and others may see the checksum fail on the first
* attempt. Let's give it a few tries
*/
for (i = 0;; i++) {
if (e1000_validate_nvm_checksum(&adapter->hw) >= 0)
break;
if (i == 2) {
e_err("The NVM Checksum Is Not Valid\n");
err = -EIO;
goto err_eeprom;
}
}
e1000_eeprom_checks(adapter);
/* copy the MAC address 拷贝mac地址*/
if (e1000e_read_mac_addr(&adapter->hw))
e_err("NVM Read Error while reading MAC address\n");
memcpy(netdev->dev_addr, adapter->hw.mac.addr, netdev->addr_len);
memcpy(netdev->perm_addr, adapter->hw.mac.addr, netdev->addr_len);
if (!is_valid_ether_addr(netdev->perm_addr)) {
e_err("Invalid MAC Address: %pM\n", netdev->perm_addr);
err = -EIO;
goto err_eeprom;
}
init_timer(&adapter->watchdog_timer);
adapter->watchdog_timer.function = &e1000_watchdog;
adapter->watchdog_timer.data = (unsigned long) adapter;
init_timer(&adapter->phy_info_timer);
adapter->phy_info_timer.function = &e1000_update_phy_info;
adapter->phy_info_timer.data = (unsigned long) adapter;
INIT_WORK(&adapter->reset_task, e1000_reset_task);
INIT_WORK(&adapter->watchdog_task, e1000_watchdog_task);
INIT_WORK(&adapter->downshift_task, e1000e_downshift_workaround);
INIT_WORK(&adapter->update_phy_task, e1000e_update_phy_task);
INIT_WORK(&adapter->print_hang_task, e1000_print_hw_hang);
/* Initialize link parameters. User can change them with ethtool */
adapter->hw.mac.autoneg = 1;
adapter->fc_autoneg = 1;
adapter->hw.fc.requested_mode = e1000_fc_default;
adapter->hw.fc.current_mode = e1000_fc_default;
adapter->hw.phy.autoneg_advertised = 0x2f;
//设置接收环型缓冲区队列的缺省大小
/* ring size defaults */
adapter->rx_ring->count = 256;
adapter->tx_ring->count = 256;
/*
* Initial Wake on LAN setting - If APM wake is enabled in
* the EEPROM, enable the ACPI Magic Packet filter
*/
if (adapter->flags & FLAG_APME_IN_WUC) {
/* APME bit in EEPROM is mapped to WUC.APME */
eeprom_data = er32(WUC);
eeprom_apme_mask = E1000_WUC_APME;
if (eeprom_data & E1000_WUC_PHY_WAKE)
adapter->flags2 |= FLAG2_HAS_PHY_WAKEUP;
} else if (adapter->flags & FLAG_APME_IN_CTRL3) {
if (adapter->flags & FLAG_APME_CHECK_PORT_B &&
(adapter->hw.bus.func == 1))
e1000_read_nvm(&adapter->hw,
NVM_INIT_CONTROL3_PORT_B, 1, &eeprom_data);
else
e1000_read_nvm(&adapter->hw,
NVM_INIT_CONTROL3_PORT_A, 1, &eeprom_data);
}
/* fetch WoL from EEPROM */
if (eeprom_data & eeprom_apme_mask)
adapter->eeprom_wol |= E1000_WUFC_MAG;
/*
* now that we have the eeprom settings, apply the special cases
* where the eeprom may be wrong or the board simply won't support
* wake on lan on a particular port
*/
if (!(adapter->flags & FLAG_HAS_WOL))
adapter->eeprom_wol = 0;
/* initialize the wol settings based on the eeprom settings */
adapter->wol = adapter->eeprom_wol;
device_set_wakeup_enable(&adapter->pdev->dev, adapter->wol);
/* save off EEPROM version number */
e1000_read_nvm(&adapter->hw, 5, 1, &adapter->eeprom_vers);
/* reset the hardware with the new settings */
e1000e_reset(adapter);
/*
* If the controller has AMT, do not set DRV_LOAD until the interface
* is up. For all other cases, let the f/w know that the h/w is now
* under the control of the driver.
*/
if (!(adapter->flags & FLAG_HAS_AMT))
e1000_get_hw_control(adapter);
strcpy(netdev->name, "eth%d");
//将当前网络设备注册到系统的dev_base[]设备数组当中
err = register_netdev(netdev);
if (err)
goto err_register;
/* carrier off reporting is important to ethtool even BEFORE open */
netif_carrier_off(netdev);
e1000_print_device_info(adapter);
if (pci_dev_run_wake(pdev)) {
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
}
pm_schedule_suspend(&pdev->dev, MSEC_PER_SEC);
return 0;
err_register:
if (!(adapter->flags & FLAG_HAS_AMT))
e1000_release_hw_control(adapter);
err_eeprom:
if (!e1000_check_reset_block(&adapter->hw))
e1000_phy_hw_reset(&adapter->hw);
err_hw_init:
kfree(adapter->tx_ring);
kfree(adapter->rx_ring);
err_sw_init:
if (adapter->hw.flash_address)
iounmap(adapter->hw.flash_address);
e1000e_reset_interrupt_capability(adapter);
err_flashmap:
iounmap(adapter->hw.hw_addr);
err_ioremap:
free_netdev(netdev);
err_alloc_etherdev:
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
err_pci_reg:
err_dma:
pci_disable_device(pdev);
return err;
}
e1000网卡注册的操作函数
static const struct net_device_ops e1000e_netdev_ops = {
//打卡网卡
.ndo_open = e1000_open,
//关闭网卡
.ndo_stop = e1000_close,
//发送函数
.ndo_start_xmit = e1000_xmit_frame,
.ndo_get_stats = e1000_get_stats,
.ndo_set_multicast_list = e1000_set_multi,
.ndo_set_mac_address = e1000_set_mac,
.ndo_change_mtu = e1000_change_mtu,
.ndo_do_ioctl = e1000_ioctl,
.ndo_tx_timeout = e1000_tx_timeout,
.ndo_validate_addr = eth_validate_addr,
.ndo_vlan_rx_register = e1000_vlan_rx_register,
.ndo_vlan_rx_add_vid = e1000_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = e1000_vlan_rx_kill_vid,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = e1000_netpoll,
#endif
};
(5)e1000_open函数
e1000_open函数主要是打开网卡 做一些初始化的工作,主要工作有初始化发送缓冲区、接受缓冲区、中断使能。代码如下
static int e1000_open(struct net_device *netdev)
{
struct e1000_adapter *adapter = netdev_priv(netdev);
struct e1000_hw *hw = &adapter->hw;
struct pci_dev *pdev = adapter->pdev;
int err;
/* disallow open during test */
if (test_bit(__E1000_TESTING, &adapter->state))
return -EBUSY;
pm_runtime_get_sync(&pdev->dev);
netif_carrier_off(netdev);
/* allocate transmit descriptors 初始化发送缓冲区*/
err = e1000e_setup_tx_resources(adapter);
if (err)
goto err_setup_tx;
/* allocate receive descriptors初始化接受缓冲区 */
err = e1000e_setup_rx_resources(adapter);
if (err)
goto err_setup_rx;
/*
* If AMT is enabled, let the firmware know that the network
* interface is now open and reset the part to a known state.
*/
if (adapter->flags & FLAG_HAS_AMT) {
e1000_get_hw_control(adapter);
e1000e_reset(adapter);
}
e1000e_power_up_phy(adapter);
adapter->mng_vlan_id = E1000_MNG_VLAN_NONE;
if ((adapter->hw.mng_cookie.status &
E1000_MNG_DHCP_COOKIE_STATUS_VLAN))
e1000_update_mng_vlan(adapter);
/*
* before we allocate an interrupt, we must be ready to handle it.
* Setting DEBUG_SHIRQ in the kernel makes it fire an interrupt
* as soon as we call pci_request_irq, so we have to setup our
* clean_rx handler before we do so.
*/
//对容器的配置包括发送、接受、中断
e1000_configure(adapter);
//中断申请
err = e1000_request_irq(adapter);
if (err)
goto err_req_irq;
/*
* Work around PCIe errata with MSI interrupts causing some chipsets to
* ignore e1000e MSI messages, which means we need to test our MSI
* interrupt now
*/
if (adapter->int_mode != E1000E_INT_MODE_LEGACY) {
err = e1000_test_msi(adapter);
if (err) {
e_err("Interrupt allocation failed\n");
goto err_req_irq;
}
}
/* From here on the code is the same as e1000e_up() */
clear_bit(__E1000_DOWN, &adapter->state);
napi_enable(&adapter->napi);
//中断使能
e1000_irq_enable(adapter);
//开始队列
netif_start_queue(netdev);
adapter->idle_check = true;
pm_runtime_put(&pdev->dev);
/* fire a link status change interrupt to start the watchdog */
if (adapter->msix_entries)
ew32(ICS, E1000_ICS_LSC | E1000_ICR_OTHER);
else
ew32(ICS, E1000_ICS_LSC);
return 0;
err_req_irq:
e1000_release_hw_control(adapter);
e1000_power_down_phy(adapter);
e1000e_free_rx_resources(adapter);
err_setup_rx:
e1000e_free_tx_resources(adapter);
err_setup_tx:
e1000e_reset(adapter);
pm_runtime_put_sync(&pdev->dev);
return err;
}
2、e1000发包流程
网卡的发包函数流程如下:-> dev_queue_xmit()
->dev_hard_start_xmit()
->ops->ndo_start_xmit(skb, dev)
ndo_start_xmit就是我们之前注册的e1000_xmit_frame(),e1000中主要的两个函数是e1000_tx_map()和e1000_tx_queue(),
e1000_xmit_frame代码如下:
static netdev_tx_t e1000_xmit_frame(struct sk_buff *skb,
struct net_device *netdev)
{
struct e1000_adapter *adapter = netdev_priv(netdev);
struct e1000_ring *tx_ring = adapter->tx_ring;
unsigned int first;
unsigned int max_per_txd = E1000_MAX_PER_TXD;
unsigned int max_txd_pwr = E1000_MAX_TXD_PWR;
unsigned int tx_flags = 0;
unsigned int len = skb_headlen(skb);
unsigned int nr_frags;
unsigned int mss;
int count = 0;
int tso;
unsigned int f;
if (test_bit(__E1000_DOWN, &adapter->state)) {
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
}
if (skb->len <= 0) {
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
}
mss = skb_shinfo(skb)->gso_size;
/*
* The controller does a simple calculation to
* make sure there is enough room in the FIFO before
* initiating the DMA for each buffer. The calc is:
* 4 = ceil(buffer len/mss). To make sure we don't
* overrun the FIFO, adjust the max buffer len if mss
* drops.
*/
if (mss) {
u8 hdr_len;
max_per_txd = min(mss << 2, max_per_txd);
max_txd_pwr = fls(max_per_txd) - 1;
/*
* TSO Workaround for 82571/2/3 Controllers -- if skb->data
* points to just header, pull a few bytes of payload from
* frags into skb->data
*/
hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);
/*
* we do this workaround for ES2LAN, but it is un-necessary,
* avoiding it could save a lot of cycles
*/
if (skb->data_len && (hdr_len == len)) {
unsigned int pull_size;
pull_size = min((unsigned int)4, skb->data_len);
if (!__pskb_pull_tail(skb, pull_size)) {
e_err("__pskb_pull_tail failed.\n");
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
}
len = skb_headlen(skb);
}
}
/* reserve a descriptor for the offload context */
if ((mss) || (skb->ip_summed == CHECKSUM_PARTIAL))
count++;
count++;
count += TXD_USE_COUNT(len, max_txd_pwr);
nr_frags = skb_shinfo(skb)->nr_frags;
for (f = 0; f < nr_frags; f++)
count += TXD_USE_COUNT(skb_shinfo(skb)->frags[f].size,
max_txd_pwr);
if (adapter->hw.mac.tx_pkt_filtering)
e1000_transfer_dhcp_info(adapter, skb);
/*
* need: count + 2 desc gap to keep tail from touching
* head, otherwise try next time
*/
if (e1000_maybe_stop_tx(netdev, count + 2))
return NETDEV_TX_BUSY;
if (adapter->vlgrp && vlan_tx_tag_present(skb)) {
tx_flags |= E1000_TX_FLAGS_VLAN;
tx_flags |= (vlan_tx_tag_get(skb) << E1000_TX_FLAGS_VLAN_SHIFT);
}
first = tx_ring->next_to_use;
tso = e1000_tso(adapter, skb);
if (tso < 0) {
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
}
if (tso)
tx_flags |= E1000_TX_FLAGS_TSO;
else if (e1000_tx_csum(adapter, skb))
tx_flags |= E1000_TX_FLAGS_CSUM;
/*
* Old method was to assume IPv4 packet by default if TSO was enabled.
* 82571 hardware supports TSO capabilities for IPv6 as well...
* no longer assume, we must.
*/
if (skb->protocol == htons(ETH_P_IP))
tx_flags |= E1000_TX_FLAGS_IPV4;
/*数据做dma地址映射*/
/* if count is 0 then mapping error has occured */
count = e1000_tx_map(adapter, skb, first, max_per_txd, nr_frags, mss);
if (count) {
/*最终发送函数*/
e1000_tx_queue(adapter, tx_flags, count);
/* Make sure there is space in the ring for the next send. */
e1000_maybe_stop_tx(netdev, MAX_SKB_FRAGS + 2);
} else {
dev_kfree_skb_any(skb);
tx_ring->buffer_info[first].time_stamp = 0;
tx_ring->next_to_use = first;
}
return NETDEV_TX_OK;
}
e1000_tx_map主要做的是把数据包的虚拟地址映射为dma的物理地址,代码如下:
static int e1000_tx_map(struct e1000_adapter *adapter,
struct sk_buff *skb, unsigned int first,
unsigned int max_per_txd, unsigned int nr_frags,
unsigned int mss)
{
struct e1000_ring *tx_ring = adapter->tx_ring;
struct pci_dev *pdev = adapter->pdev;
struct e1000_buffer *buffer_info;
unsigned int len = skb_headlen(skb);
unsigned int offset = 0, size, count = 0, i;
unsigned int f, bytecount, segs;
i = tx_ring->next_to_use;
while (len) {
buffer_info = &tx_ring->buffer_info[i];
size = min(len, max_per_txd);
buffer_info->length = size;
//时间戳
buffer_info->time_stamp = jiffies;
buffer_info->next_to_watch = i;
//将skb->data的虚拟地址转换为pci域的物理地址
buffer_info->dma = dma_map_single(&pdev->dev,
skb->data + offset,
size, DMA_TO_DEVICE);
buffer_info->mapped_as_page = false;
if (dma_mapping_error(&pdev->dev, buffer_info->dma))
goto dma_error;
len -= size;
offset += size;
count++;
if (len) {
i++;
if (i == tx_ring->count)
i = 0;
}
}
e1000_tx_queue最终的发送函数,它的只要是调用write去dma物理地址取包,接下来的工作就交给硬件了,代码分析如下。
static void e1000_tx_queue(struct e1000_adapter *adapter,
int tx_flags, int count)
{
struct e1000_ring *tx_ring = adapter->tx_ring;
struct e1000_tx_desc *tx_desc = NULL;
struct e1000_buffer *buffer_info;
u32 txd_upper = 0, txd_lower = E1000_TXD_CMD_IFCS;
unsigned int i;
if (tx_flags & E1000_TX_FLAGS_TSO) {
txd_lower |= E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D |
E1000_TXD_CMD_TSE;
txd_upper |= E1000_TXD_POPTS_TXSM << 8;
if (tx_flags & E1000_TX_FLAGS_IPV4)
txd_upper |= E1000_TXD_POPTS_IXSM << 8;
}
if (tx_flags & E1000_TX_FLAGS_CSUM) {
txd_lower |= E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D;
txd_upper |= E1000_TXD_POPTS_TXSM << 8;
}
if (tx_flags & E1000_TX_FLAGS_VLAN) {
txd_lower |= E1000_TXD_CMD_VLE;
txd_upper |= (tx_flags & E1000_TX_FLAGS_VLAN_MASK);
}
i = tx_ring->next_to_use;
while (count--) {
buffer_info = &tx_ring->buffer_info[i];
tx_desc = E1000_TX_DESC(*tx_ring, i);
//存放dma地址(实际的物理地址)
tx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
tx_desc->lower.data =
cpu_to_le32(txd_lower | buffer_info->length);
tx_desc->upper.data = cpu_to_le32(txd_upper);
i++;
if (i == tx_ring->count)
i = 0;
}
tx_desc->lower.data |= cpu_to_le32(adapter->txd_cmd);
/*
* Force memory writes to complete before letting h/w
* know there are new descriptors to fetch. (Only
* applicable for weak-ordered memory model archs,
* such as IA-64).
*/
wmb();
tx_ring->next_to_use = i;
//写入发送的地址空间
writel(i, adapter->hw.hw_addr + tx_ring->tail);
/*
* we need this if more than one processor can write to our tail
* at a time, it synchronizes IO on IA64/Altix systems
*/
mmiowb();
}
3、e1000收包流程