目录
1 驱动模块注册
2 PCI相关初始化
2.1 PCI 驱动列表注册:pci_register_driver()
2.2 搜索和加载驱动:pci_driver->probe()
2.2.1 rtl8125_try_msi
2.2.2 rtl8125_init_napi
2.2.3 rtl8125_init_all_schedule_work
3 以太网相关初始化
3.1 net_device_ops
3.2 注册 ethtool
1 驱动模块注册
module_init() 注册一个初始化函数,加载驱动时执行。
module_init(rtl8125_init_module);
static int __init
rtl8125_init_module(void)
{
int ret = 0;
#ifdef ENABLE_R8125_PROCFS
rtl8125_proc_module_init();
#endif
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
/*初始化PCI相关的内容*/
ret = pci_register_driver(&rtl8125_pci_driver);
#else
ret = pci_module_init(&rtl8125_pci_driver);
#endif
return ret;
}
rtl8125_proc_module_init() 就是在/proc/net下新建一个文件夹,用于存放rtl8125相关的信息。
static void rtl8125_proc_module_init(void)
{
//create /proc/net/r8125
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
rtl8125_proc = proc_mkdir(MODULENAME, init_net.proc_net);
#else
rtl8125_proc = proc_mkdir(MODULENAME, proc_net);
#endif
if (!rtl8125_proc)
dprintk("cannot create %s proc entry \n", MODULENAME);
}
初始化的大部分工作在pci_register_driver中进行。
2 PCI相关初始化
2.1 PCI 驱动列表注册:pci_register_driver()
Realtek 8125是PCIe设备,这种设备设备通过 PCI Configuration Space 识别。当设备驱动编译时,MODULE_DEVICE_TABLE 宏会导出一个 global 的 PCI 设备 ID 列表, 驱动据此识别它可以控制哪些设备,这样内核就能对各设备加载正确的驱动。8125驱动的设备表和PCI设备ID如下:
static struct pci_device_id rtl8125_pci_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8125), },
{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8162), },
{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x3000), },
{0,},
};
MODULE_DEVICE_TABLE(pci, rtl8125_pci_tbl);
pci_register_driver() 会将该驱动的各种回调方法注册到一个 struct pci_driver rtl8125_pci_driver 变量:
static struct pci_driver rtl8125_pci_driver = {
.name = MODULENAME,
.id_table = rtl8125_pci_tbl,
.probe = rtl8125_init_one, //初始化时执行这个函数
.remove = __devexit_p(rtl8125_remove_one),
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,11)
.shutdown = rtl8125_shutdown,
#endif
#ifdef CONFIG_PM
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
.suspend = rtl8125_suspend,
.resume = rtl8125_resume,
#else
.driver.pm = RTL8125_PM_OPS,
#endif
#endif
};
2.2 搜索和加载驱动:pci_driver->probe()
内核会通过 PCI ID 依次识别各 PCI 设备,然后为设备选择合适的驱动。 每个 PCI 驱动都注册了一个 probe() 方法,为设备寻找驱动就是调用其 probe() 方法。
来看下 r8125 驱动的 probe() 包含哪些过程:
static int __devinit
rtl8125_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct net_device *dev = NULL;
struct rtl8125_private *tp;
void __iomem *ioaddr = NULL;
static int board_idx = -1;
int rc;
assert(pdev != NULL);
assert(ent != NULL);
board_idx++;
if (netif_msg_drv(&debug))
printk(KERN_INFO "%s 2.5Gigabit Ethernet driver %s loaded\n",
MODULENAME, RTL8125_VERSION);
rc = rtl8125_init_board(pdev, &dev, &ioaddr);
if (rc)
goto out;
tp = netdev_priv(dev);
assert(ioaddr != NULL);
//设置一些函数
tp->set_speed = rtl8125_set_speed_xmii;
tp->get_settings = rtl8125_gset_xmii;
tp->phy_reset_enable = rtl8125_xmii_reset_enable;
tp->phy_reset_pending = rtl8125_xmii_reset_pending;
tp->link_ok = rtl8125_xmii_link_ok;
rc = rtl8125_try_msi(tp);
if (rc < 0) {
dev_err(&pdev->dev, "Can't allocate interrupt\n");
goto err_out_1;
}
#ifdef ENABLE_PTP_SUPPORT
spin_lock_init(&tp->lock);
#endif
/* 初始化一些软件参数,即rtl8125_private结构的*tp*/
rtl8125_init_software_variable(dev);
/* 这里要注意 */
RTL_NET_DEVICE_OPS(rtl8125_netdev_ops);
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,22)
/* ethtool相关 */
SET_ETHTOOL_OPS(dev, &rtl8125_ethtool_ops);
#endif
dev->watchdog_timeo = RTL8125_TX_TIMEOUT;
dev->irq = rtl8125_get_irq(pdev);
dev->base_addr = (unsigned long) ioaddr;
rtl8125_init_napi(tp);
#ifdef CONFIG_R8125_VLAN
if (tp->mcfg != CFG_METHOD_DEFAULT) {
dev->features |= NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_RX;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
dev->vlan_rx_kill_vid = rtl8125_vlan_rx_kill_vid;
#endif //LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
}
#endif
/* There has been a number of reports that using SG/TSO results in
* tx timeouts. However for a lot of people SG/TSO works fine.
* Therefore disable both features by default, but allow users to
* enable them. Use at own risk!
*/
tp->cp_cmd |= RTL_R16(tp, CPlusCmd);
if (tp->mcfg != CFG_METHOD_DEFAULT) {
dev->features |= NETIF_F_IP_CSUM;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
tp->cp_cmd |= RxChkSum;
#else
dev->features |= NETIF_F_RXCSUM;
dev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |
NETIF_F_RXCSUM | NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_RX;
dev->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |
NETIF_F_HIGHDMA;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0)
dev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
#endif //LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0)
dev->hw_features |= NETIF_F_RXALL;
dev->hw_features |= NETIF_F_RXFCS;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
dev->hw_features |= NETIF_F_IPV6_CSUM | NETIF_F_TSO6;
dev->features |= NETIF_F_IPV6_CSUM;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,19,0)
netif_set_tso_max_size(dev, LSO_64K);
netif_set_tso_max_segs(dev, NIC_MAX_PHYS_BUF_COUNT_LSO2);
#else //LINUX_VERSION_CODE >= KERNEL_VERSION(5,19,0)
netif_set_gso_max_size(dev, LSO_64K);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,0)
dev->gso_max_segs = NIC_MAX_PHYS_BUF_COUNT_LSO2;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
dev->gso_min_segs = NIC_MIN_PHYS_BUF_COUNT;
#endif //LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
#endif //LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,0)
#endif //LINUX_VERSION_CODE >= KERNEL_VERSION(5,19,0)
#endif //LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
#endif //LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
#ifdef ENABLE_RSS_SUPPORT
if (tp->EnableRss) {
dev->hw_features |= NETIF_F_RXHASH;
dev->features |= NETIF_F_RXHASH;
}
#endif
}
#ifdef ENABLE_DASH_SUPPORT
if (tp->DASH)
AllocateDashShareMemory(dev);
#endif
#ifdef ENABLE_LIB_SUPPORT
ATOMIC_INIT_NOTIFIER_HEAD(&tp->lib_nh);
#endif
rtl8125_init_all_schedule_work(tp);
rc = rtl8125_set_real_num_queue(tp);
if (rc < 0)
goto err_out;
rtl8125_exit_oob(dev);
rtl8125_powerup_pll(dev);
rtl8125_hw_init(dev);
rtl8125_hw_reset(dev);
/* Get production from EEPROM */
rtl8125_eeprom_type(tp);
if (tp->eeprom_type == EEPROM_TYPE_93C46 || tp->eeprom_type == EEPROM_TYPE_93C56)
rtl8125_set_eeprom_sel_low(tp);
rtl8125_get_mac_address(dev);
tp->fw_name = rtl_chip_fw_infos[tp->mcfg].fw_name;
tp->tally_vaddr = dma_alloc_coherent(&pdev->dev, sizeof(*tp->tally_vaddr),
&tp->tally_paddr, GFP_KERNEL);
if (!tp->tally_vaddr) {
rc = -ENOMEM;
goto err_out;
}
rtl8125_tally_counter_clear(tp);
pci_set_drvdata(pdev, dev);
rc = register_netdev(dev);
if (rc)
goto err_out;
printk(KERN_INFO "%s: This product is covered by one or more of the following patents: US6,570,884, US6,115,776, and US6,327,625.\n", MODULENAME);
rtl8125_disable_rxdvgate(dev);
device_set_wakeup_enable(&pdev->dev, tp->wol_enabled);
netif_carrier_off(dev);
printk("%s", GPL_CLAIM);
out:
return rc;
err_out:
if (tp->tally_vaddr != NULL) {
dma_free_coherent(&pdev->dev, sizeof(*tp->tally_vaddr), tp->tally_vaddr,
tp->tally_paddr);
tp->tally_vaddr = NULL;
}
#ifdef CONFIG_R8125_NAPI
rtl8125_del_napi(tp);
#endif
rtl8125_disable_msi(pdev, tp);
err_out_1:
rtl8125_release_board(pdev, dev);
goto out;
}
2.2.1 rtl8125_try_msi
当一个数据帧通过 DMA 写到内核内存 ringbuffer 后,网卡通过硬件中断(IRQ)通知其他系统。 设备有三种方式触发一个中断:
(1)MSI-X
(2)MSI
(3)legacy interrupts
设备驱动的实现也因此而异。驱动必须判断出设备支持哪种中断方式,然后注册相应的中断处理函数,这些函数在中断发生的时候会被执行。
MSI-X 中断是比较推荐的方式,尤其是对于支持多队列的网卡。 因为每个 RX 队列有独立的 MSI-X 中断,因此可以被不同的CPU处理(通过irqbalance 方式,或者修改 /proc/irq/IRQ_NUMBER/smp_affinity)。后面会看到 ,处理中断的 CPU 也是随后处理这个包的 CPU。这样的话,从网卡硬件中断的层面就可以设置让收到的包被不同的 CPU 处理。
如果不支持 MSI-X,那 MSI 相比于传统中断方式仍然有一些优势,驱动仍然会优先考虑它。
8125支援多队列,一般是使用MSI-X的中断方式。
static int rtl8125_try_msi(struct rtl8125_private *tp)
{
struct pci_dev *pdev = tp->pci_dev;
unsigned msi = 0;
int nvecs = 1;
tp->max_irq_nvecs = 1;
tp->min_irq_nvecs = 1;
#ifndef DISABLE_MULTI_MSIX_VECTOR
switch (tp->mcfg) {
case CFG_METHOD_4:
case CFG_METHOD_5:
case CFG_METHOD_7:
tp->max_irq_nvecs = R8125_MAX_MSIX_VEC_8125B;
tp->min_irq_nvecs = R8125_MIN_MSIX_VEC_8125B;
break;
}
#endif
#if defined(RTL_USE_NEW_INTR_API)
if ((nvecs = pci_alloc_irq_vectors(pdev, tp->min_irq_nvecs, tp->max_irq_nvecs, PCI_IRQ_MSIX)) > 0)
msi |= RTL_FEATURE_MSIX;
else if ((nvecs = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES)) > 0 &&
pci_dev_msi_enabled(pdev))
msi |= RTL_FEATURE_MSI;
#elif LINUX_VERSION_CODE > KERNEL_VERSION(2,6,13)
if ((nvecs = rtl8125_enable_msix(tp)) > 0)
msi |= RTL_FEATURE_MSIX;
else if (!pci_enable_msi(pdev))
msi |= RTL_FEATURE_MSI;
#endif
if (!(msi & (RTL_FEATURE_MSI | RTL_FEATURE_MSIX)))
dev_info(&pdev->dev, "no MSI/MSI-X. Back to INTx.\n");
if (!(msi & RTL_FEATURE_MSIX) || nvecs < 1)
nvecs = 1;
tp->irq_nvecs = nvecs;
tp->features |= msi;
return nvecs;
}
2.2.2 rtl8125_init_napi
NAPI是综合中断方式与轮询方式的技术。数据量低时采用中断,数据量高时采用轮询。平时是中断方式,当有数据到达时,会触发中断处理函数执行,中断处理函数关闭中断开始处理。如果此时有数据到达,则没必要再触发中断了,因为中断处理函数中会轮询处理数据,直到没有新数据时才打开中断。这里分别init了tx和rx的poll函数。
static void rtl8125_init_napi(struct rtl8125_private *tp)
{
int i;
for (i=0; i<tp->irq_nvecs; i++) {
struct r8125_napi *r8125napi = &tp->r8125napi[i];
#ifdef CONFIG_R8125_NAPI
int (*poll)(struct napi_struct *, int);
if (tp->features & RTL_FEATURE_MSIX &&
tp->HwCurrIsrVer == 2) {
if (i < R8125_MAX_RX_QUEUES_VEC_V3)
poll = rtl8125_poll_msix_rx;
else if (i == 16 || i == 18)
poll = rtl8125_poll_msix_tx;
else
poll = rtl8125_poll_msix_other;
} else {
poll = rtl8125_poll;
}
RTL_NAPI_CONFIG(tp->dev, r8125napi, poll, R8125_NAPI_WEIGHT);
#endif
r8125napi->priv = tp;
r8125napi->index = i;
}
}
2.2.3 rtl8125_init_all_schedule_work
这里是在初始化工作队列,它是将操作(或回调)延期异步执行的一种机制。工作队列可以把工作推后,交由一个内核线程去执行,并且工作队列是执行在线程上下文中,因此工作执行过程中可以被重新调度、抢占、睡眠。
static void rtl8125_init_all_schedule_work(struct rtl8125_private *tp)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
INIT_WORK(&tp->reset_task, rtl8125_reset_task, dev);
INIT_WORK(&tp->esd_task, rtl8125_esd_task, dev);
INIT_WORK(&tp->linkchg_task, rtl8125_linkchg_task, dev);
#else
INIT_DELAYED_WORK(&tp->reset_task, rtl8125_reset_task);
INIT_DELAYED_WORK(&tp->esd_task, rtl8125_esd_task);
INIT_DELAYED_WORK(&tp->linkchg_task, rtl8125_linkchg_task);
#endif
}
3 以太网相关初始化
3.1 net_device_ops
struct net_device_ops 可以通过链接里的内容细看,This structure defines the management hooks for network devices。
static const struct net_device_ops rtl8125_netdev_ops = {
.ndo_open = rtl8125_open,
.ndo_stop = rtl8125_close,
.ndo_get_stats = rtl8125_get_stats,
.ndo_start_xmit = rtl8125_start_xmit,
.ndo_tx_timeout = rtl8125_tx_timeout,
.ndo_change_mtu = rtl8125_change_mtu,
.ndo_set_mac_address = rtl8125_set_mac_address,
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,15,0)
.ndo_do_ioctl = rtl8125_do_ioctl,
#else
.ndo_siocdevprivate = rtl8125_siocdevprivate,
.ndo_eth_ioctl = rtl8125_do_ioctl,
#endif //LINUX_VERSION_CODE < KERNEL_VERSION(5,15,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,1,0)
.ndo_set_multicast_list = rtl8125_set_rx_mode,
#else
.ndo_set_rx_mode = rtl8125_set_rx_mode,
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
#ifdef CONFIG_R8125_VLAN
.ndo_vlan_rx_register = rtl8125_vlan_rx_register,
#endif
#else
.ndo_fix_features = rtl8125_fix_features,
.ndo_set_features = rtl8125_set_features,
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = rtl8125_netpoll,
#endif
};
这里注意:
/*
* int (*ndo_open)(struct net_device *dev);
* This function is called when a network device transitions to the up
* state.
*/
所以,函数rtl8125_open() 会被调用。
int rtl8125_open(struct net_device *dev)
{
struct rtl8125_private *tp = netdev_priv(dev);
int retval;
retval = -ENOMEM;
#ifdef ENABLE_R8125_PROCFS
/* rtl8125可以通过cat /proc/net/r8125/ethx/查看一些信息 */
rtl8125_proc_init(dev);
#endif
/* set rx buffer size */
rtl8125_set_rxbufsize(tp, dev);
/*
* Rx and Tx descriptors needs 256 bytes alignment.
* pci_alloc_consistent provides more.
*/
/* alloc trx desc */
if (rtl8125_alloc_tx_desc(tp) < 0 || rtl8125_alloc_rx_desc(tp) < 0)
goto err_free_all_allocated_mem;
/* 初始化ring,比如ring的一些变量,初始化ring buffer等等*/
retval = rtl8125_init_ring(dev);
if (retval < 0)
goto err_free_all_allocated_mem;
retval = rtl8125_alloc_patch_mem(tp);
if (retval < 0)
goto err_free_all_allocated_mem;
/* 指定中断处理函数等 */
retval = rtl8125_alloc_irq(tp);
if (retval < 0)
goto err_free_all_allocated_mem;
if (netif_msg_probe(tp)) {
printk(KERN_INFO "%s: 0x%lx, "
"%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, "
"IRQ %d\n",
dev->name,
dev->base_addr,
dev->dev_addr[0], dev->dev_addr[1],
dev->dev_addr[2], dev->dev_addr[3],
dev->dev_addr[4], dev->dev_addr[5], dev->irq);
}
#ifdef ENABLE_USE_FIRMWARE_FILE
rtl8125_request_firmware(tp);
#endif
/* enables bus-mastering for device dev */
pci_set_master(tp->pci_dev);
#ifdef CONFIG_R8125_NAPI
rtl8125_enable_napi(tp);
#endif
rtl8125_exit_oob(dev);
rtl8125_up(dev);
#ifdef ENABLE_PTP_SUPPORT
if (tp->EnablePtp)
rtl8125_ptp_init(tp);
#endif
clear_bit(R8125_FLAG_DOWN, tp->task_flags);
if (tp->resume_not_chg_speed)
rtl8125_check_link_status(dev);
else
rtl8125_set_speed(dev, tp->autoneg, tp->speed, tp->duplex, tp->advertising);
if (tp->esd_flag == 0) {
//rtl8125_request_esd_timer(dev);
rtl8125_schedule_esd_work(tp);
}
//rtl8125_request_link_timer(dev);
rtl8125_enable_hw_linkchg_interrupt(tp);
out:
return retval;
err_free_all_allocated_mem:
rtl8125_free_alloc_resources(tp);
goto out;
}
3.2 注册 ethtool
static const struct ethtool_ops rtl8125_ethtool_ops = {
.get_drvinfo = rtl8125_get_drvinfo,
.get_regs_len = rtl8125_get_regs_len,
.get_link = ethtool_op_get_link,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
.get_ringparam = rtl8125_get_ringparam,
.set_ringparam = rtl8125_set_ringparam,
#endif //LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,6,0)
.get_settings = rtl8125_get_settings,
.set_settings = rtl8125_set_settings,
#else
.get_link_ksettings = rtl8125_get_settings,
.set_link_ksettings = rtl8125_set_settings,
#endif //LINUX_VERSION_CODE < KERNEL_VERSION(4,6,0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
.get_pauseparam = rtl8125_get_pauseparam,
.set_pauseparam = rtl8125_set_pauseparam,
#endif //LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
.get_msglevel = rtl8125_get_msglevel,
.set_msglevel = rtl8125_set_msglevel,
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,3,0)
.get_rx_csum = rtl8125_get_rx_csum,
.set_rx_csum = rtl8125_set_rx_csum,
.get_tx_csum = rtl8125_get_tx_csum,
.set_tx_csum = rtl8125_set_tx_csum,
.get_sg = ethtool_op_get_sg,
.set_sg = ethtool_op_set_sg,
#ifdef NETIF_F_TSO
.get_tso = ethtool_op_get_tso,
.set_tso = ethtool_op_set_tso,
#endif //NETIF_F_TSO
#endif //LINUX_VERSION_CODE < KERNEL_VERSION(3,3,0)
.get_regs = rtl8125_get_regs,
.get_wol = rtl8125_get_wol,
.set_wol = rtl8125_set_wol,
.get_strings = rtl8125_get_strings,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
.get_stats_count = rtl8125_get_stats_count,
#else
.get_sset_count = rtl8125_get_sset_count,
#endif //LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
.get_ethtool_stats = rtl8125_get_ethtool_stats,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
#ifdef ETHTOOL_GPERMADDR
.get_perm_addr = ethtool_op_get_perm_addr,
#endif //ETHTOOL_GPERMADDR
#endif //LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
.get_eeprom = rtl_get_eeprom,
.get_eeprom_len = rtl_get_eeprom_len,
#ifdef ENABLE_RSS_SUPPORT
.get_rxnfc = rtl8125_get_rxnfc,
.set_rxnfc = rtl8125_set_rxnfc,
.get_rxfh_indir_size = rtl8125_rss_indir_size,
.get_rxfh_key_size = rtl8125_get_rxfh_key_size,
.get_rxfh = rtl8125_get_rxfh,
.set_rxfh = rtl8125_set_rxfh,
#endif //ENABLE_RSS_SUPPORT
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
#ifdef ENABLE_PTP_SUPPORT
.get_ts_info = rtl8125_get_ts_info,
#else
.get_ts_info = ethtool_op_get_ts_info,
#endif //ENABLE_PTP_SUPPORT
#endif //LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)
.get_eee = rtl_ethtool_get_eee,
.set_eee = rtl_ethtool_set_eee,
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0) */
.nway_reset = rtl_nway_reset,
};