| /* |
| * Copyright (C) 2005 - 2008 ServerEngines |
| * All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation. The full GNU General |
| * Public License is included in this distribution in the file called COPYING. |
| * |
| * Contact Information: |
| * linux-drivers@serverengines.com |
| * |
| * ServerEngines |
| * 209 N. Fair Oaks Ave |
| * Sunnyvale, CA 94085 |
| */ |
| #include <linux/etherdevice.h> |
| #include "benet.h" |
| |
| #define DRVR_VERSION "1.0.728" |
| |
| static const struct pci_device_id be_device_id_table[] = { |
| {PCI_DEVICE(0x19a2, 0x0201)}, |
| {0} |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, be_device_id_table); |
| |
| MODULE_VERSION(DRVR_VERSION); |
| |
| #define DRV_DESCRIPTION "ServerEngines BladeEngine Network Driver Version " |
| |
| MODULE_DESCRIPTION(DRV_DESCRIPTION DRVR_VERSION); |
| MODULE_AUTHOR("ServerEngines"); |
| MODULE_LICENSE("GPL"); |
| |
| static unsigned int msix = 1; |
| module_param(msix, uint, S_IRUGO); |
| MODULE_PARM_DESC(msix, "Use MSI-x interrupts"); |
| |
| static unsigned int rxbuf_size = 2048; /* Default RX frag size */ |
| module_param(rxbuf_size, uint, S_IRUGO); |
| MODULE_PARM_DESC(rxbuf_size, "Size of buffers to hold Rx data"); |
| |
| const char be_drvr_ver[] = DRVR_VERSION; |
| char be_fw_ver[32]; /* F/W version filled in by be_probe */ |
| char be_driver_name[] = "benet"; |
| |
| /* |
| * Number of entries in each queue. |
| */ |
| #define EVENT_Q_LEN 1024 |
| #define ETH_TXQ_LEN 2048 |
| #define ETH_TXCQ_LEN 1024 |
| #define ETH_RXQ_LEN 1024 /* Does not support any other value */ |
| #define ETH_UC_RXCQ_LEN 1024 |
| #define ETH_BC_RXCQ_LEN 256 |
| #define MCC_Q_LEN 64 /* total size not to exceed 8 pages */ |
| #define MCC_CQ_LEN 256 |
| |
| /* Bit mask describing events of interest to be traced */ |
| unsigned int trace_level; |
| |
| static int |
| init_pci_be_function(struct be_adapter *adapter, struct pci_dev *pdev) |
| { |
| u64 pa; |
| |
| /* CSR */ |
| pa = pci_resource_start(pdev, 2); |
| adapter->csr_va = ioremap_nocache(pa, pci_resource_len(pdev, 2)); |
| if (adapter->csr_va == NULL) |
| return -ENOMEM; |
| |
| /* Door Bell */ |
| pa = pci_resource_start(pdev, 4); |
| adapter->db_va = ioremap_nocache(pa, (128 * 1024)); |
| if (adapter->db_va == NULL) { |
| iounmap(adapter->csr_va); |
| return -ENOMEM; |
| } |
| |
| /* PCI */ |
| pa = pci_resource_start(pdev, 1); |
| adapter->pci_va = ioremap_nocache(pa, pci_resource_len(pdev, 1)); |
| if (adapter->pci_va == NULL) { |
| iounmap(adapter->csr_va); |
| iounmap(adapter->db_va); |
| return -ENOMEM; |
| } |
| return 0; |
| } |
| |
| /* |
| This function enables the interrupt corresponding to the Event |
| queue ID for the given NetObject |
| */ |
| void be_enable_eq_intr(struct be_net_object *pnob) |
| { |
| struct CQ_DB_AMAP cqdb; |
| cqdb.dw[0] = 0; |
| AMAP_SET_BITS_PTR(CQ_DB, event, &cqdb, 1); |
| AMAP_SET_BITS_PTR(CQ_DB, rearm, &cqdb, 1); |
| AMAP_SET_BITS_PTR(CQ_DB, num_popped, &cqdb, 0); |
| AMAP_SET_BITS_PTR(CQ_DB, qid, &cqdb, pnob->event_q_id); |
| PD_WRITE(&pnob->fn_obj, cq_db, cqdb.dw[0]); |
| } |
| |
| /* |
| This function disables the interrupt corresponding to the Event |
| queue ID for the given NetObject |
| */ |
| void be_disable_eq_intr(struct be_net_object *pnob) |
| { |
| struct CQ_DB_AMAP cqdb; |
| cqdb.dw[0] = 0; |
| AMAP_SET_BITS_PTR(CQ_DB, event, &cqdb, 1); |
| AMAP_SET_BITS_PTR(CQ_DB, rearm, &cqdb, 0); |
| AMAP_SET_BITS_PTR(CQ_DB, num_popped, &cqdb, 0); |
| AMAP_SET_BITS_PTR(CQ_DB, qid, &cqdb, pnob->event_q_id); |
| PD_WRITE(&pnob->fn_obj, cq_db, cqdb.dw[0]); |
| } |
| |
| /* |
| This function enables the interrupt from the network function |
| of the BladeEngine. Use the function be_disable_eq_intr() |
| to enable the interrupt from the event queue of only one specific |
| NetObject |
| */ |
| void be_enable_intr(struct be_net_object *pnob) |
| { |
| struct PCICFG_HOST_TIMER_INT_CTRL_CSR_AMAP ctrl; |
| u32 host_intr; |
| |
| ctrl.dw[0] = PCICFG1_READ(&pnob->fn_obj, host_timer_int_ctrl); |
| host_intr = AMAP_GET_BITS_PTR(PCICFG_HOST_TIMER_INT_CTRL_CSR, |
| hostintr, ctrl.dw); |
| if (!host_intr) { |
| AMAP_SET_BITS_PTR(PCICFG_HOST_TIMER_INT_CTRL_CSR, |
| hostintr, ctrl.dw, 1); |
| PCICFG1_WRITE(&pnob->fn_obj, host_timer_int_ctrl, |
| ctrl.dw[0]); |
| } |
| } |
| |
| /* |
| This function disables the interrupt from the network function of |
| the BladeEngine. Use the function be_disable_eq_intr() to |
| disable the interrupt from the event queue of only one specific NetObject |
| */ |
| void be_disable_intr(struct be_net_object *pnob) |
| { |
| |
| struct PCICFG_HOST_TIMER_INT_CTRL_CSR_AMAP ctrl; |
| u32 host_intr; |
| ctrl.dw[0] = PCICFG1_READ(&pnob->fn_obj, host_timer_int_ctrl); |
| host_intr = AMAP_GET_BITS_PTR(PCICFG_HOST_TIMER_INT_CTRL_CSR, |
| hostintr, ctrl.dw); |
| if (host_intr) { |
| AMAP_SET_BITS_PTR(PCICFG_HOST_TIMER_INT_CTRL_CSR, hostintr, |
| ctrl.dw, 0); |
| PCICFG1_WRITE(&pnob->fn_obj, host_timer_int_ctrl, |
| ctrl.dw[0]); |
| } |
| } |
| |
| static int be_enable_msix(struct be_adapter *adapter) |
| { |
| int i, ret; |
| |
| if (!msix) |
| return -1; |
| |
| for (i = 0; i < BE_MAX_REQ_MSIX_VECTORS; i++) |
| adapter->msix_entries[i].entry = i; |
| |
| ret = pci_enable_msix(adapter->pdev, adapter->msix_entries, |
| BE_MAX_REQ_MSIX_VECTORS); |
| |
| if (ret == 0) |
| adapter->msix_enabled = 1; |
| return ret; |
| } |
| |
| static int be_register_isr(struct be_adapter *adapter, |
| struct be_net_object *pnob) |
| { |
| struct net_device *netdev = pnob->netdev; |
| int intx = 0, r; |
| |
| netdev->irq = adapter->pdev->irq; |
| r = be_enable_msix(adapter); |
| |
| if (r == 0) { |
| r = request_irq(adapter->msix_entries[0].vector, |
| be_int, IRQF_SHARED, netdev->name, netdev); |
| if (r) { |
| printk(KERN_WARNING |
| "MSIX Request IRQ failed - Errno %d\n", r); |
| intx = 1; |
| pci_disable_msix(adapter->pdev); |
| adapter->msix_enabled = 0; |
| } |
| } else { |
| intx = 1; |
| } |
| |
| if (intx) { |
| r = request_irq(netdev->irq, be_int, IRQF_SHARED, |
| netdev->name, netdev); |
| if (r) { |
| printk(KERN_WARNING |
| "INTx Request IRQ failed - Errno %d\n", r); |
| return -1; |
| } |
| } |
| adapter->isr_registered = 1; |
| return 0; |
| } |
| |
| static void be_unregister_isr(struct be_adapter *adapter) |
| { |
| struct net_device *netdev = adapter->netdevp; |
| if (adapter->isr_registered) { |
| if (adapter->msix_enabled) { |
| free_irq(adapter->msix_entries[0].vector, netdev); |
| pci_disable_msix(adapter->pdev); |
| adapter->msix_enabled = 0; |
| } else { |
| free_irq(netdev->irq, netdev); |
| } |
| adapter->isr_registered = 0; |
| } |
| } |
| |
| /* |
| This function processes the Flush Completions that are issued by the |
| ARM F/W, when a Recv Ring is destroyed. A flush completion is |
| identified when a Rx COmpl descriptor has the tcpcksum and udpcksum |
| set and the pktsize is 32. These completions are received on the |
| Rx Completion Queue. |
| */ |
| static u32 be_process_rx_flush_cmpl(struct be_net_object *pnob) |
| { |
| struct ETH_RX_COMPL_AMAP *rxcp; |
| unsigned int i = 0; |
| while ((rxcp = be_get_rx_cmpl(pnob)) != NULL) { |
| be_notify_cmpl(pnob, 1, pnob->rx_cq_id, 1); |
| i++; |
| } |
| return i; |
| } |
| |
| static void be_tx_q_clean(struct be_net_object *pnob) |
| { |
| while (atomic_read(&pnob->tx_q_used)) |
| process_one_tx_compl(pnob, tx_compl_lastwrb_idx_get(pnob)); |
| } |
| |
| static void be_rx_q_clean(struct be_net_object *pnob) |
| { |
| if (pnob->rx_ctxt) { |
| int i; |
| struct be_rx_page_info *rx_page_info; |
| for (i = 0; i < pnob->rx_q_len; i++) { |
| rx_page_info = &(pnob->rx_page_info[i]); |
| if (!pnob->rx_pg_shared || rx_page_info->page_offset) { |
| pci_unmap_page(pnob->adapter->pdev, |
| pci_unmap_addr(rx_page_info, bus), |
| pnob->rx_buf_size, |
| PCI_DMA_FROMDEVICE); |
| } |
| if (rx_page_info->page) |
| put_page(rx_page_info->page); |
| memset(rx_page_info, 0, sizeof(struct be_rx_page_info)); |
| } |
| pnob->rx_pg_info_hd = 0; |
| } |
| } |
| |
| static void be_destroy_netobj(struct be_net_object *pnob) |
| { |
| int status; |
| |
| if (pnob->tx_q_created) { |
| status = be_eth_sq_destroy(&pnob->tx_q_obj); |
| pnob->tx_q_created = 0; |
| } |
| |
| if (pnob->rx_q_created) { |
| status = be_eth_rq_destroy(&pnob->rx_q_obj); |
| if (status != 0) { |
| status = be_eth_rq_destroy_options(&pnob->rx_q_obj, 0, |
| NULL, NULL); |
| BUG_ON(status); |
| } |
| pnob->rx_q_created = 0; |
| } |
| |
| be_process_rx_flush_cmpl(pnob); |
| |
| if (pnob->tx_cq_created) { |
| status = be_cq_destroy(&pnob->tx_cq_obj); |
| pnob->tx_cq_created = 0; |
| } |
| |
| if (pnob->rx_cq_created) { |
| status = be_cq_destroy(&pnob->rx_cq_obj); |
| pnob->rx_cq_created = 0; |
| } |
| |
| if (pnob->mcc_q_created) { |
| status = be_mcc_ring_destroy(&pnob->mcc_q_obj); |
| pnob->mcc_q_created = 0; |
| } |
| if (pnob->mcc_cq_created) { |
| status = be_cq_destroy(&pnob->mcc_cq_obj); |
| pnob->mcc_cq_created = 0; |
| } |
| |
| if (pnob->event_q_created) { |
| status = be_eq_destroy(&pnob->event_q_obj); |
| pnob->event_q_created = 0; |
| } |
| be_function_cleanup(&pnob->fn_obj); |
| } |
| |
| /* |
| * free all resources associated with a pnob |
| * Called at the time of module cleanup as well a any error during |
| * module init. Some resources may be partially allocated in a NetObj. |
| */ |
| static void netobject_cleanup(struct be_adapter *adapter, |
| struct be_net_object *pnob) |
| { |
| struct net_device *netdev = adapter->netdevp; |
| |
| if (netif_running(netdev)) { |
| netif_stop_queue(netdev); |
| be_wait_nic_tx_cmplx_cmpl(pnob); |
| be_disable_eq_intr(pnob); |
| } |
| |
| be_unregister_isr(adapter); |
| |
| if (adapter->tasklet_started) { |
| tasklet_kill(&(adapter->sts_handler)); |
| adapter->tasklet_started = 0; |
| } |
| if (pnob->fn_obj_created) |
| be_disable_intr(pnob); |
| |
| if (adapter->dev_state != BE_DEV_STATE_NONE) |
| unregister_netdev(netdev); |
| |
| if (pnob->fn_obj_created) |
| be_destroy_netobj(pnob); |
| |
| adapter->net_obj = NULL; |
| adapter->netdevp = NULL; |
| |
| be_rx_q_clean(pnob); |
| if (pnob->rx_ctxt) { |
| kfree(pnob->rx_page_info); |
| kfree(pnob->rx_ctxt); |
| } |
| |
| be_tx_q_clean(pnob); |
| kfree(pnob->tx_ctxt); |
| |
| if (pnob->mcc_q) |
| pci_free_consistent(adapter->pdev, pnob->mcc_q_size, |
| pnob->mcc_q, pnob->mcc_q_bus); |
| |
| if (pnob->mcc_wrb_ctxt) |
| free_pages((unsigned long)pnob->mcc_wrb_ctxt, |
| get_order(pnob->mcc_wrb_ctxt_size)); |
| |
| if (pnob->mcc_cq) |
| pci_free_consistent(adapter->pdev, pnob->mcc_cq_size, |
| pnob->mcc_cq, pnob->mcc_cq_bus); |
| |
| if (pnob->event_q) |
| pci_free_consistent(adapter->pdev, pnob->event_q_size, |
| pnob->event_q, pnob->event_q_bus); |
| |
| if (pnob->tx_cq) |
| pci_free_consistent(adapter->pdev, pnob->tx_cq_size, |
| pnob->tx_cq, pnob->tx_cq_bus); |
| |
| if (pnob->tx_q) |
| pci_free_consistent(adapter->pdev, pnob->tx_q_size, |
| pnob->tx_q, pnob->tx_q_bus); |
| |
| if (pnob->rx_q) |
| pci_free_consistent(adapter->pdev, pnob->rx_q_size, |
| pnob->rx_q, pnob->rx_q_bus); |
| |
| if (pnob->rx_cq) |
| pci_free_consistent(adapter->pdev, pnob->rx_cq_size, |
| pnob->rx_cq, pnob->rx_cq_bus); |
| |
| |
| if (pnob->mb_ptr) |
| pci_free_consistent(adapter->pdev, pnob->mb_size, pnob->mb_ptr, |
| pnob->mb_bus); |
| |
| free_netdev(netdev); |
| } |
| |
| |
| static int be_nob_ring_alloc(struct be_adapter *adapter, |
| struct be_net_object *pnob) |
| { |
| u32 size; |
| |
| /* Mail box rd; mailbox pointer needs to be 16 byte aligned */ |
| pnob->mb_size = sizeof(struct MCC_MAILBOX_AMAP) + 16; |
| pnob->mb_ptr = pci_alloc_consistent(adapter->pdev, pnob->mb_size, |
| &pnob->mb_bus); |
| if (!pnob->mb_bus) |
| return -1; |
| memset(pnob->mb_ptr, 0, pnob->mb_size); |
| pnob->mb_rd.va = PTR_ALIGN(pnob->mb_ptr, 16); |
| pnob->mb_rd.pa = PTR_ALIGN(pnob->mb_bus, 16); |
| pnob->mb_rd.length = sizeof(struct MCC_MAILBOX_AMAP); |
| /* |
| * Event queue |
| */ |
| pnob->event_q_len = EVENT_Q_LEN; |
| pnob->event_q_size = pnob->event_q_len * sizeof(struct EQ_ENTRY_AMAP); |
| pnob->event_q = pci_alloc_consistent(adapter->pdev, pnob->event_q_size, |
| &pnob->event_q_bus); |
| if (!pnob->event_q_bus) |
| return -1; |
| memset(pnob->event_q, 0, pnob->event_q_size); |
| /* |
| * Eth TX queue |
| */ |
| pnob->tx_q_len = ETH_TXQ_LEN; |
| pnob->tx_q_port = 0; |
| pnob->tx_q_size = pnob->tx_q_len * sizeof(struct ETH_WRB_AMAP); |
| pnob->tx_q = pci_alloc_consistent(adapter->pdev, pnob->tx_q_size, |
| &pnob->tx_q_bus); |
| if (!pnob->tx_q_bus) |
| return -1; |
| memset(pnob->tx_q, 0, pnob->tx_q_size); |
| /* |
| * Eth TX Compl queue |
| */ |
| pnob->txcq_len = ETH_TXCQ_LEN; |
| pnob->tx_cq_size = pnob->txcq_len * sizeof(struct ETH_TX_COMPL_AMAP); |
| pnob->tx_cq = pci_alloc_consistent(adapter->pdev, pnob->tx_cq_size, |
| &pnob->tx_cq_bus); |
| if (!pnob->tx_cq_bus) |
| return -1; |
| memset(pnob->tx_cq, 0, pnob->tx_cq_size); |
| /* |
| * Eth RX queue |
| */ |
| pnob->rx_q_len = ETH_RXQ_LEN; |
| pnob->rx_q_size = pnob->rx_q_len * sizeof(struct ETH_RX_D_AMAP); |
| pnob->rx_q = pci_alloc_consistent(adapter->pdev, pnob->rx_q_size, |
| &pnob->rx_q_bus); |
| if (!pnob->rx_q_bus) |
| return -1; |
| memset(pnob->rx_q, 0, pnob->rx_q_size); |
| /* |
| * Eth Unicast RX Compl queue |
| */ |
| pnob->rx_cq_len = ETH_UC_RXCQ_LEN; |
| pnob->rx_cq_size = pnob->rx_cq_len * |
| sizeof(struct ETH_RX_COMPL_AMAP); |
| pnob->rx_cq = pci_alloc_consistent(adapter->pdev, pnob->rx_cq_size, |
| &pnob->rx_cq_bus); |
| if (!pnob->rx_cq_bus) |
| return -1; |
| memset(pnob->rx_cq, 0, pnob->rx_cq_size); |
| |
| /* TX resources */ |
| size = pnob->tx_q_len * sizeof(void **); |
| pnob->tx_ctxt = kzalloc(size, GFP_KERNEL); |
| if (pnob->tx_ctxt == NULL) |
| return -1; |
| |
| /* RX resources */ |
| size = pnob->rx_q_len * sizeof(void *); |
| pnob->rx_ctxt = kzalloc(size, GFP_KERNEL); |
| if (pnob->rx_ctxt == NULL) |
| return -1; |
| |
| size = (pnob->rx_q_len * sizeof(struct be_rx_page_info)); |
| pnob->rx_page_info = kzalloc(size, GFP_KERNEL); |
| if (pnob->rx_page_info == NULL) |
| return -1; |
| |
| adapter->eth_statsp = kzalloc(sizeof(struct FWCMD_ETH_GET_STATISTICS), |
| GFP_KERNEL); |
| if (adapter->eth_statsp == NULL) |
| return -1; |
| pnob->rx_buf_size = rxbuf_size; |
| return 0; |
| } |
| |
| /* |
| This function initializes the be_net_object for subsequent |
| network operations. |
| |
| Before calling this function, the driver must have allocated |
| space for the NetObject structure, initialized the structure, |
| allocated DMAable memory for all the network queues that form |
| part of the NetObject and populated the start address (virtual) |
| and number of entries allocated for each queue in the NetObject structure. |
| |
| The driver must also have allocated memory to hold the |
| mailbox structure (MCC_MAILBOX) and post the physical address, |
| virtual addresses and the size of the mailbox memory in the |
| NetObj.mb_rd. This structure is used by BECLIB for |
| initial communication with the embedded MCC processor. BECLIB |
| uses the mailbox until MCC rings are created for more efficient |
| communication with the MCC processor. |
| |
| If the driver wants to create multiple network interface for more |
| than one protection domain, it can call be_create_netobj() |
| multiple times once for each protection domain. A Maximum of |
| 32 protection domains are supported. |
| |
| */ |
| static int |
| be_create_netobj(struct be_net_object *pnob, u8 __iomem *csr_va, |
| u8 __iomem *db_va, u8 __iomem *pci_va) |
| { |
| int status = 0; |
| bool eventable = false, tx_no_delay = false, rx_no_delay = false; |
| struct be_eq_object *eq_objectp = NULL; |
| struct be_function_object *pfob = &pnob->fn_obj; |
| struct ring_desc rd; |
| u32 set_rxbuf_size; |
| u32 tx_cmpl_wm = CEV_WMARK_96; /* 0xffffffff to disable */ |
| u32 rx_cmpl_wm = CEV_WMARK_160; /* 0xffffffff to disable */ |
| u32 eq_delay = 0; /* delay in 8usec units. 0xffffffff to disable */ |
| |
| memset(&rd, 0, sizeof(struct ring_desc)); |
| |
| status = be_function_object_create(csr_va, db_va, pci_va, |
| BE_FUNCTION_TYPE_NETWORK, &pnob->mb_rd, pfob); |
| if (status != BE_SUCCESS) |
| return status; |
| pnob->fn_obj_created = true; |
| |
| if (tx_cmpl_wm == 0xffffffff) |
| tx_no_delay = true; |
| if (rx_cmpl_wm == 0xffffffff) |
| rx_no_delay = true; |
| /* |
| * now create the necessary rings |
| * Event Queue first. |
| */ |
| if (pnob->event_q_len) { |
| rd.va = pnob->event_q; |
| rd.pa = pnob->event_q_bus; |
| rd.length = pnob->event_q_size; |
| |
| status = be_eq_create(pfob, &rd, 4, pnob->event_q_len, |
| (u32) -1, /* CEV_WMARK_* or -1 */ |
| eq_delay, /* in 8us units, or -1 */ |
| &pnob->event_q_obj); |
| if (status != BE_SUCCESS) |
| goto error_ret; |
| pnob->event_q_id = pnob->event_q_obj.eq_id; |
| pnob->event_q_created = 1; |
| eventable = true; |
| eq_objectp = &pnob->event_q_obj; |
| } |
| /* |
| * Now Eth Tx Compl. queue. |
| */ |
| if (pnob->txcq_len) { |
| rd.va = pnob->tx_cq; |
| rd.pa = pnob->tx_cq_bus; |
| rd.length = pnob->tx_cq_size; |
| |
| status = be_cq_create(pfob, &rd, |
| pnob->txcq_len * sizeof(struct ETH_TX_COMPL_AMAP), |
| false, /* solicted events, */ |
| tx_no_delay, /* nodelay */ |
| tx_cmpl_wm, /* Watermark encodings */ |
| eq_objectp, &pnob->tx_cq_obj); |
| if (status != BE_SUCCESS) |
| goto error_ret; |
| |
| pnob->tx_cq_id = pnob->tx_cq_obj.cq_id; |
| pnob->tx_cq_created = 1; |
| } |
| /* |
| * Eth Tx queue |
| */ |
| if (pnob->tx_q_len) { |
| struct be_eth_sq_parameters ex_params = { 0 }; |
| u32 type; |
| |
| if (pnob->tx_q_port) { |
| /* TXQ to be bound to a specific port */ |
| type = BE_ETH_TX_RING_TYPE_BOUND; |
| ex_params.port = pnob->tx_q_port - 1; |
| } else |
| type = BE_ETH_TX_RING_TYPE_STANDARD; |
| |
| rd.va = pnob->tx_q; |
| rd.pa = pnob->tx_q_bus; |
| rd.length = pnob->tx_q_size; |
| |
| status = be_eth_sq_create_ex(pfob, &rd, |
| pnob->tx_q_len * sizeof(struct ETH_WRB_AMAP), |
| type, 2, &pnob->tx_cq_obj, |
| &ex_params, &pnob->tx_q_obj); |
| |
| if (status != BE_SUCCESS) |
| goto error_ret; |
| |
| pnob->tx_q_id = pnob->tx_q_obj.bid; |
| pnob->tx_q_created = 1; |
| } |
| /* |
| * Now Eth Rx compl. queue. Always needed. |
| */ |
| rd.va = pnob->rx_cq; |
| rd.pa = pnob->rx_cq_bus; |
| rd.length = pnob->rx_cq_size; |
| |
| status = be_cq_create(pfob, &rd, |
| pnob->rx_cq_len * sizeof(struct ETH_RX_COMPL_AMAP), |
| false, /* solicted events, */ |
| rx_no_delay, /* nodelay */ |
| rx_cmpl_wm, /* Watermark encodings */ |
| eq_objectp, &pnob->rx_cq_obj); |
| if (status != BE_SUCCESS) |
| goto error_ret; |
| |
| pnob->rx_cq_id = pnob->rx_cq_obj.cq_id; |
| pnob->rx_cq_created = 1; |
| |
| status = be_eth_rq_set_frag_size(pfob, pnob->rx_buf_size, |
| (u32 *) &set_rxbuf_size); |
| if (status != BE_SUCCESS) { |
| be_eth_rq_get_frag_size(pfob, (u32 *) &pnob->rx_buf_size); |
| if ((pnob->rx_buf_size != 2048) && (pnob->rx_buf_size != 4096) |
| && (pnob->rx_buf_size != 8192)) |
| goto error_ret; |
| } else { |
| if (pnob->rx_buf_size != set_rxbuf_size) |
| pnob->rx_buf_size = set_rxbuf_size; |
| } |
| /* |
| * Eth RX queue. be_eth_rq_create() always assumes 2 pages size |
| */ |
| rd.va = pnob->rx_q; |
| rd.pa = pnob->rx_q_bus; |
| rd.length = pnob->rx_q_size; |
| |
| status = be_eth_rq_create(pfob, &rd, &pnob->rx_cq_obj, |
| &pnob->rx_cq_obj, &pnob->rx_q_obj); |
| |
| if (status != BE_SUCCESS) |
| goto error_ret; |
| |
| pnob->rx_q_id = pnob->rx_q_obj.rid; |
| pnob->rx_q_created = 1; |
| |
| return BE_SUCCESS; /* All required queues created. */ |
| |
| error_ret: |
| be_destroy_netobj(pnob); |
| return status; |
| } |
| |
| static int be_nob_ring_init(struct be_adapter *adapter, |
| struct be_net_object *pnob) |
| { |
| int status; |
| |
| pnob->event_q_tl = 0; |
| |
| pnob->tx_q_hd = 0; |
| pnob->tx_q_tl = 0; |
| |
| pnob->tx_cq_tl = 0; |
| |
| pnob->rx_cq_tl = 0; |
| |
| memset(pnob->event_q, 0, pnob->event_q_size); |
| memset(pnob->tx_cq, 0, pnob->tx_cq_size); |
| memset(pnob->tx_ctxt, 0, pnob->tx_q_len * sizeof(void **)); |
| memset(pnob->rx_ctxt, 0, pnob->rx_q_len * sizeof(void *)); |
| pnob->rx_pg_info_hd = 0; |
| pnob->rx_q_hd = 0; |
| atomic_set(&pnob->rx_q_posted, 0); |
| |
| status = be_create_netobj(pnob, adapter->csr_va, adapter->db_va, |
| adapter->pci_va); |
| if (status != BE_SUCCESS) |
| return -1; |
| |
| be_post_eth_rx_buffs(pnob); |
| return 0; |
| } |
| |
| /* This function handles async callback for link status */ |
| static void |
| be_link_status_async_callback(void *context, u32 event_code, void *event) |
| { |
| struct ASYNC_EVENT_LINK_STATE_AMAP *link_status = event; |
| struct be_adapter *adapter = context; |
| bool link_enable = false; |
| struct be_net_object *pnob; |
| struct ASYNC_EVENT_TRAILER_AMAP *async_trailer; |
| struct net_device *netdev; |
| u32 async_event_code, async_event_type, active_port; |
| u32 port0_link_status, port1_link_status, port0_duplex, port1_duplex; |
| u32 port0_speed, port1_speed; |
| |
| if (event_code != ASYNC_EVENT_CODE_LINK_STATE) { |
| /* Not our event to handle */ |
| return; |
| } |
| async_trailer = (struct ASYNC_EVENT_TRAILER_AMAP *) |
| ((u8 *) event + sizeof(struct MCC_CQ_ENTRY_AMAP) - |
| sizeof(struct ASYNC_EVENT_TRAILER_AMAP)); |
| |
| async_event_code = AMAP_GET_BITS_PTR(ASYNC_EVENT_TRAILER, event_code, |
| async_trailer); |
| BUG_ON(async_event_code != ASYNC_EVENT_CODE_LINK_STATE); |
| |
| pnob = adapter->net_obj; |
| netdev = pnob->netdev; |
| |
| /* Determine if this event is a switch VLD or a physical link event */ |
| async_event_type = AMAP_GET_BITS_PTR(ASYNC_EVENT_TRAILER, event_type, |
| async_trailer); |
| active_port = AMAP_GET_BITS_PTR(ASYNC_EVENT_LINK_STATE, |
| active_port, link_status); |
| port0_link_status = AMAP_GET_BITS_PTR(ASYNC_EVENT_LINK_STATE, |
| port0_link_status, link_status); |
| port1_link_status = AMAP_GET_BITS_PTR(ASYNC_EVENT_LINK_STATE, |
| port1_link_status, link_status); |
| port0_duplex = AMAP_GET_BITS_PTR(ASYNC_EVENT_LINK_STATE, |
| port0_duplex, link_status); |
| port1_duplex = AMAP_GET_BITS_PTR(ASYNC_EVENT_LINK_STATE, |
| port1_duplex, link_status); |
| port0_speed = AMAP_GET_BITS_PTR(ASYNC_EVENT_LINK_STATE, |
| port0_speed, link_status); |
| port1_speed = AMAP_GET_BITS_PTR(ASYNC_EVENT_LINK_STATE, |
| port1_speed, link_status); |
| if (async_event_type == NTWK_LINK_TYPE_VIRTUAL) { |
| adapter->be_stat.bes_link_change_virtual++; |
| if (adapter->be_link_sts->active_port != active_port) { |
| dev_notice(&netdev->dev, |
| "Active port changed due to VLD on switch\n"); |
| } else { |
| dev_notice(&netdev->dev, "Link status update\n"); |
| } |
| |
| } else { |
| adapter->be_stat.bes_link_change_physical++; |
| if (adapter->be_link_sts->active_port != active_port) { |
| dev_notice(&netdev->dev, |
| "Active port changed due to port link" |
| " status change\n"); |
| } else { |
| dev_notice(&netdev->dev, "Link status update\n"); |
| } |
| } |
| |
| memset(adapter->be_link_sts, 0, sizeof(adapter->be_link_sts)); |
| |
| if ((port0_link_status == ASYNC_EVENT_LINK_UP) || |
| (port1_link_status == ASYNC_EVENT_LINK_UP)) { |
| if ((adapter->port0_link_sts == BE_PORT_LINK_DOWN) && |
| (adapter->port1_link_sts == BE_PORT_LINK_DOWN)) { |
| /* Earlier both the ports are down So link is up */ |
| link_enable = true; |
| } |
| |
| if (port0_link_status == ASYNC_EVENT_LINK_UP) { |
| adapter->port0_link_sts = BE_PORT_LINK_UP; |
| adapter->be_link_sts->mac0_duplex = port0_duplex; |
| adapter->be_link_sts->mac0_speed = port0_speed; |
| if (active_port == NTWK_PORT_A) |
| adapter->be_link_sts->active_port = 0; |
| } else |
| adapter->port0_link_sts = BE_PORT_LINK_DOWN; |
| |
| if (port1_link_status == ASYNC_EVENT_LINK_UP) { |
| adapter->port1_link_sts = BE_PORT_LINK_UP; |
| adapter->be_link_sts->mac1_duplex = port1_duplex; |
| adapter->be_link_sts->mac1_speed = port1_speed; |
| if (active_port == NTWK_PORT_B) |
| adapter->be_link_sts->active_port = 1; |
| } else |
| adapter->port1_link_sts = BE_PORT_LINK_DOWN; |
| |
| printk(KERN_INFO "Link Properties for %s:\n", netdev->name); |
| dev_info(&netdev->dev, "Link Properties:\n"); |
| be_print_link_info(adapter->be_link_sts); |
| |
| if (!link_enable) |
| return; |
| /* |
| * Both ports were down previously, but atleast one of |
| * them has come up if this netdevice's carrier is not up, |
| * then indicate to stack |
| */ |
| if (!netif_carrier_ok(netdev)) { |
| netif_start_queue(netdev); |
| netif_carrier_on(netdev); |
| } |
| return; |
| } |
| |
| /* Now both the ports are down. Tell the stack about it */ |
| dev_info(&netdev->dev, "Both ports are down\n"); |
| adapter->port0_link_sts = BE_PORT_LINK_DOWN; |
| adapter->port1_link_sts = BE_PORT_LINK_DOWN; |
| if (netif_carrier_ok(netdev)) { |
| netif_carrier_off(netdev); |
| netif_stop_queue(netdev); |
| } |
| return; |
| } |
| |
| static int be_mcc_create(struct be_adapter *adapter) |
| { |
| struct be_net_object *pnob; |
| |
| pnob = adapter->net_obj; |
| /* |
| * Create the MCC ring so that all further communication with |
| * MCC can go thru the ring. we do this at the end since |
| * we do not want to be dealing with interrupts until the |
| * initialization is complete. |
| */ |
| pnob->mcc_q_len = MCC_Q_LEN; |
| pnob->mcc_q_size = pnob->mcc_q_len * sizeof(struct MCC_WRB_AMAP); |
| pnob->mcc_q = pci_alloc_consistent(adapter->pdev, pnob->mcc_q_size, |
| &pnob->mcc_q_bus); |
| if (!pnob->mcc_q_bus) |
| return -1; |
| /* |
| * space for MCC WRB context |
| */ |
| pnob->mcc_wrb_ctxtLen = MCC_Q_LEN; |
| pnob->mcc_wrb_ctxt_size = pnob->mcc_wrb_ctxtLen * |
| sizeof(struct be_mcc_wrb_context); |
| pnob->mcc_wrb_ctxt = (void *)__get_free_pages(GFP_KERNEL, |
| get_order(pnob->mcc_wrb_ctxt_size)); |
| if (pnob->mcc_wrb_ctxt == NULL) |
| return -1; |
| /* |
| * Space for MCC compl. ring |
| */ |
| pnob->mcc_cq_len = MCC_CQ_LEN; |
| pnob->mcc_cq_size = pnob->mcc_cq_len * sizeof(struct MCC_CQ_ENTRY_AMAP); |
| pnob->mcc_cq = pci_alloc_consistent(adapter->pdev, pnob->mcc_cq_size, |
| &pnob->mcc_cq_bus); |
| if (!pnob->mcc_cq_bus) |
| return -1; |
| return 0; |
| } |
| |
| /* |
| This function creates the MCC request and completion ring required |
| for communicating with the ARM processor. The caller must have |
| allocated required amount of memory for the MCC ring and MCC |
| completion ring and posted the virtual address and number of |
| entries in the corresponding members (mcc_q and mcc_cq) in the |
| NetObject struture. |
| |
| When this call is completed, all further communication with |
| ARM will switch from mailbox to this ring. |
| |
| pnob - Pointer to the NetObject structure. This NetObject should |
| have been created using a previous call to be_create_netobj() |
| */ |
| int be_create_mcc_rings(struct be_net_object *pnob) |
| { |
| int status = 0; |
| struct ring_desc rd; |
| struct be_function_object *pfob = &pnob->fn_obj; |
| |
| memset(&rd, 0, sizeof(struct ring_desc)); |
| if (pnob->mcc_cq_len) { |
| rd.va = pnob->mcc_cq; |
| rd.pa = pnob->mcc_cq_bus; |
| rd.length = pnob->mcc_cq_size; |
| |
| status = be_cq_create(pfob, &rd, |
| pnob->mcc_cq_len * sizeof(struct MCC_CQ_ENTRY_AMAP), |
| false, /* solicted events, */ |
| true, /* nodelay */ |
| 0, /* 0 Watermark since Nodelay is true */ |
| &pnob->event_q_obj, |
| &pnob->mcc_cq_obj); |
| |
| if (status != BE_SUCCESS) |
| return status; |
| |
| pnob->mcc_cq_id = pnob->mcc_cq_obj.cq_id; |
| pnob->mcc_cq_created = 1; |
| } |
| if (pnob->mcc_q_len) { |
| rd.va = pnob->mcc_q; |
| rd.pa = pnob->mcc_q_bus; |
| rd.length = pnob->mcc_q_size; |
| |
| status = be_mcc_ring_create(pfob, &rd, |
| pnob->mcc_q_len * sizeof(struct MCC_WRB_AMAP), |
| pnob->mcc_wrb_ctxt, pnob->mcc_wrb_ctxtLen, |
| &pnob->mcc_cq_obj, &pnob->mcc_q_obj); |
| |
| if (status != BE_SUCCESS) |
| return status; |
| |
| pnob->mcc_q_created = 1; |
| } |
| return BE_SUCCESS; |
| } |
| |
| static int be_mcc_init(struct be_adapter *adapter) |
| { |
| u32 r; |
| struct be_net_object *pnob; |
| |
| pnob = adapter->net_obj; |
| memset(pnob->mcc_q, 0, pnob->mcc_q_size); |
| pnob->mcc_q_hd = 0; |
| |
| memset(pnob->mcc_wrb_ctxt, 0, pnob->mcc_wrb_ctxt_size); |
| |
| memset(pnob->mcc_cq, 0, pnob->mcc_cq_size); |
| pnob->mcc_cq_tl = 0; |
| |
| r = be_create_mcc_rings(adapter->net_obj); |
| if (r != BE_SUCCESS) |
| return -1; |
| |
| return 0; |
| } |
| |
| static void be_remove(struct pci_dev *pdev) |
| { |
| struct be_net_object *pnob; |
| struct be_adapter *adapter; |
| |
| adapter = pci_get_drvdata(pdev); |
| if (!adapter) |
| return; |
| |
| pci_set_drvdata(pdev, NULL); |
| pnob = (struct be_net_object *)adapter->net_obj; |
| |
| flush_scheduled_work(); |
| |
| if (pnob) { |
| /* Unregister async callback function for link status updates */ |
| if (pnob->mcc_q_created) |
| be_mcc_add_async_event_callback(&pnob->mcc_q_obj, |
| NULL, NULL); |
| netobject_cleanup(adapter, pnob); |
| } |
| |
| if (adapter->csr_va) |
| iounmap(adapter->csr_va); |
| if (adapter->db_va) |
| iounmap(adapter->db_va); |
| if (adapter->pci_va) |
| iounmap(adapter->pci_va); |
| |
| pci_release_regions(adapter->pdev); |
| pci_disable_device(adapter->pdev); |
| |
| kfree(adapter->be_link_sts); |
| kfree(adapter->eth_statsp); |
| |
| if (adapter->timer_ctxt.get_stats_timer.function) |
| del_timer_sync(&adapter->timer_ctxt.get_stats_timer); |
| kfree(adapter); |
| } |
| |
| /* |
| * This function is called by the PCI sub-system when it finds a PCI |
| * device with dev/vendor IDs that match with one of our devices. |
| * All of the driver initialization is done in this function. |
| */ |
| static int be_probe(struct pci_dev *pdev, const struct pci_device_id *pdev_id) |
| { |
| int status = 0; |
| struct be_adapter *adapter; |
| struct FWCMD_COMMON_GET_FW_VERSION_RESPONSE_PAYLOAD get_fwv; |
| struct be_net_object *pnob; |
| struct net_device *netdev; |
| |
| status = pci_enable_device(pdev); |
| if (status) |
| goto error; |
| |
| status = pci_request_regions(pdev, be_driver_name); |
| if (status) |
| goto error_pci_req; |
| |
| pci_set_master(pdev); |
| adapter = kzalloc(sizeof(struct be_adapter), GFP_KERNEL); |
| if (adapter == NULL) { |
| status = -ENOMEM; |
| goto error_adapter; |
| } |
| adapter->dev_state = BE_DEV_STATE_NONE; |
| adapter->pdev = pdev; |
| pci_set_drvdata(pdev, adapter); |
| |
| adapter->enable_aic = 1; |
| adapter->max_eqd = MAX_EQD; |
| adapter->min_eqd = 0; |
| adapter->cur_eqd = 0; |
| |
| status = pci_set_dma_mask(pdev, DMA_64BIT_MASK); |
| if (!status) { |
| adapter->dma_64bit_cap = true; |
| } else { |
| adapter->dma_64bit_cap = false; |
| status = pci_set_dma_mask(pdev, DMA_32BIT_MASK); |
| if (status != 0) { |
| printk(KERN_ERR "Could not set PCI DMA Mask\n"); |
| goto cleanup; |
| } |
| } |
| |
| status = init_pci_be_function(adapter, pdev); |
| if (status != 0) { |
| printk(KERN_ERR "Failed to map PCI BARS\n"); |
| status = -ENOMEM; |
| goto cleanup; |
| } |
| |
| be_trace_set_level(DL_ALWAYS | DL_ERR); |
| |
| adapter->be_link_sts = kmalloc(sizeof(struct BE_LINK_STATUS), |
| GFP_KERNEL); |
| if (adapter->be_link_sts == NULL) { |
| printk(KERN_ERR "Memory allocation for link status " |
| "buffer failed\n"); |
| goto cleanup; |
| } |
| spin_lock_init(&adapter->txq_lock); |
| |
| netdev = alloc_etherdev(sizeof(struct be_net_object)); |
| if (netdev == NULL) { |
| status = -ENOMEM; |
| goto cleanup; |
| } |
| pnob = netdev_priv(netdev); |
| adapter->net_obj = pnob; |
| adapter->netdevp = netdev; |
| pnob->adapter = adapter; |
| pnob->netdev = netdev; |
| |
| status = be_nob_ring_alloc(adapter, pnob); |
| if (status != 0) |
| goto cleanup; |
| |
| status = be_nob_ring_init(adapter, pnob); |
| if (status != 0) |
| goto cleanup; |
| |
| be_rxf_mac_address_read_write(&pnob->fn_obj, false, false, false, |
| false, false, netdev->dev_addr, NULL, NULL); |
| |
| netdev->init = &benet_init; |
| netif_carrier_off(netdev); |
| netif_stop_queue(netdev); |
| |
| SET_NETDEV_DEV(netdev, &(adapter->pdev->dev)); |
| |
| netif_napi_add(netdev, &pnob->napi, be_poll, 64); |
| |
| /* if the rx_frag size if 2K, one page is shared as two RX frags */ |
| pnob->rx_pg_shared = |
| (pnob->rx_buf_size <= PAGE_SIZE / 2) ? true : false; |
| if (pnob->rx_buf_size != rxbuf_size) { |
| printk(KERN_WARNING |
| "Could not set Rx buffer size to %d. Using %d\n", |
| rxbuf_size, pnob->rx_buf_size); |
| rxbuf_size = pnob->rx_buf_size; |
| } |
| |
| tasklet_init(&(adapter->sts_handler), be_process_intr, |
| (unsigned long)adapter); |
| adapter->tasklet_started = 1; |
| spin_lock_init(&(adapter->int_lock)); |
| |
| status = be_register_isr(adapter, pnob); |
| if (status != 0) |
| goto cleanup; |
| |
| adapter->rx_csum = 1; |
| adapter->max_rx_coal = BE_LRO_MAX_PKTS; |
| |
| memset(&get_fwv, 0, |
| sizeof(struct FWCMD_COMMON_GET_FW_VERSION_RESPONSE_PAYLOAD)); |
| printk(KERN_INFO "BladeEngine Driver version:%s. " |
| "Copyright ServerEngines, Corporation 2005 - 2008\n", |
| be_drvr_ver); |
| status = be_function_get_fw_version(&pnob->fn_obj, &get_fwv, NULL, |
| NULL); |
| if (status == BE_SUCCESS) { |
| strncpy(be_fw_ver, get_fwv.firmware_version_string, 32); |
| printk(KERN_INFO "BladeEngine Firmware Version:%s\n", |
| get_fwv.firmware_version_string); |
| } else { |
| printk(KERN_WARNING "Unable to get BE Firmware Version\n"); |
| } |
| |
| sema_init(&adapter->get_eth_stat_sem, 0); |
| init_timer(&adapter->timer_ctxt.get_stats_timer); |
| atomic_set(&adapter->timer_ctxt.get_stat_flag, 0); |
| adapter->timer_ctxt.get_stats_timer.function = |
| &be_get_stats_timer_handler; |
| |
| status = be_mcc_create(adapter); |
| if (status < 0) |
| goto cleanup; |
| status = be_mcc_init(adapter); |
| if (status < 0) |
| goto cleanup; |
| |
| |
| status = be_mcc_add_async_event_callback(&adapter->net_obj->mcc_q_obj, |
| be_link_status_async_callback, (void *)adapter); |
| if (status != BE_SUCCESS) { |
| printk(KERN_WARNING "add_async_event_callback failed"); |
| printk(KERN_WARNING |
| "Link status changes may not be reflected\n"); |
| } |
| |
| status = register_netdev(netdev); |
| if (status != 0) |
| goto cleanup; |
| be_update_link_status(adapter); |
| adapter->dev_state = BE_DEV_STATE_INIT; |
| return 0; |
| |
| cleanup: |
| be_remove(pdev); |
| return status; |
| error_adapter: |
| pci_release_regions(pdev); |
| error_pci_req: |
| pci_disable_device(pdev); |
| error: |
| printk(KERN_ERR "BladeEngine initalization failed\n"); |
| return status; |
| } |
| |
| /* |
| * Get the current link status and print the status on console |
| */ |
| void be_update_link_status(struct be_adapter *adapter) |
| { |
| int status; |
| struct be_net_object *pnob = adapter->net_obj; |
| |
| status = be_rxf_link_status(&pnob->fn_obj, adapter->be_link_sts, NULL, |
| NULL, NULL); |
| if (status == BE_SUCCESS) { |
| if (adapter->be_link_sts->mac0_speed && |
| adapter->be_link_sts->mac0_duplex) |
| adapter->port0_link_sts = BE_PORT_LINK_UP; |
| else |
| adapter->port0_link_sts = BE_PORT_LINK_DOWN; |
| |
| if (adapter->be_link_sts->mac1_speed && |
| adapter->be_link_sts->mac1_duplex) |
| adapter->port1_link_sts = BE_PORT_LINK_UP; |
| else |
| adapter->port1_link_sts = BE_PORT_LINK_DOWN; |
| |
| dev_info(&pnob->netdev->dev, "Link Properties:\n"); |
| be_print_link_info(adapter->be_link_sts); |
| return; |
| } |
| dev_info(&pnob->netdev->dev, "Could not get link status\n"); |
| return; |
| } |
| |
| |
| #ifdef CONFIG_PM |
| static void |
| be_pm_cleanup(struct be_adapter *adapter, |
| struct be_net_object *pnob, struct net_device *netdev) |
| { |
| netif_carrier_off(netdev); |
| netif_stop_queue(netdev); |
| |
| be_wait_nic_tx_cmplx_cmpl(pnob); |
| be_disable_eq_intr(pnob); |
| |
| if (adapter->tasklet_started) { |
| tasklet_kill(&adapter->sts_handler); |
| adapter->tasklet_started = 0; |
| } |
| |
| be_unregister_isr(adapter); |
| be_disable_intr(pnob); |
| |
| be_tx_q_clean(pnob); |
| be_rx_q_clean(pnob); |
| |
| be_destroy_netobj(pnob); |
| } |
| |
| static int be_suspend(struct pci_dev *pdev, pm_message_t state) |
| { |
| struct be_adapter *adapter = pci_get_drvdata(pdev); |
| struct net_device *netdev = adapter->netdevp; |
| struct be_net_object *pnob = netdev_priv(netdev); |
| |
| adapter->dev_pm_state = adapter->dev_state; |
| adapter->dev_state = BE_DEV_STATE_SUSPEND; |
| |
| netif_device_detach(netdev); |
| if (netif_running(netdev)) |
| be_pm_cleanup(adapter, pnob, netdev); |
| |
| pci_enable_wake(pdev, 3, 1); |
| pci_enable_wake(pdev, 4, 1); /* D3 Cold = 4 */ |
| pci_save_state(pdev); |
| pci_disable_device(pdev); |
| pci_set_power_state(pdev, pci_choose_state(pdev, state)); |
| return 0; |
| } |
| |
| static void be_up(struct be_adapter *adapter) |
| { |
| struct be_net_object *pnob = adapter->net_obj; |
| |
| if (pnob->num_vlans != 0) |
| be_rxf_vlan_config(&pnob->fn_obj, false, pnob->num_vlans, |
| pnob->vlan_tag, NULL, NULL, NULL); |
| |
| } |
| |
| static int be_resume(struct pci_dev *pdev) |
| { |
| int status = 0; |
| struct be_adapter *adapter = pci_get_drvdata(pdev); |
| struct net_device *netdev = adapter->netdevp; |
| struct be_net_object *pnob = netdev_priv(netdev); |
| |
| netif_device_detach(netdev); |
| |
| status = pci_enable_device(pdev); |
| if (status) |
| return status; |
| |
| pci_set_power_state(pdev, 0); |
| pci_restore_state(pdev); |
| pci_enable_wake(pdev, 3, 0); |
| pci_enable_wake(pdev, 4, 0); /* 4 is D3 cold */ |
| |
| netif_carrier_on(netdev); |
| netif_start_queue(netdev); |
| |
| if (netif_running(netdev)) { |
| be_rxf_mac_address_read_write(&pnob->fn_obj, false, false, |
| false, true, false, netdev->dev_addr, NULL, NULL); |
| |
| status = be_nob_ring_init(adapter, pnob); |
| if (status < 0) |
| return status; |
| |
| tasklet_init(&(adapter->sts_handler), be_process_intr, |
| (unsigned long)adapter); |
| adapter->tasklet_started = 1; |
| |
| if (be_register_isr(adapter, pnob) != 0) { |
| printk(KERN_ERR "be_register_isr failed\n"); |
| return status; |
| } |
| |
| |
| status = be_mcc_init(adapter); |
| if (status < 0) { |
| printk(KERN_ERR "be_mcc_init failed\n"); |
| return status; |
| } |
| be_update_link_status(adapter); |
| /* |
| * Register async call back function to handle link |
| * status updates |
| */ |
| status = be_mcc_add_async_event_callback( |
| &adapter->net_obj->mcc_q_obj, |
| be_link_status_async_callback, (void *)adapter); |
| if (status != BE_SUCCESS) { |
| printk(KERN_WARNING "add_async_event_callback failed"); |
| printk(KERN_WARNING |
| "Link status changes may not be reflected\n"); |
| } |
| be_enable_intr(pnob); |
| be_enable_eq_intr(pnob); |
| be_up(adapter); |
| } |
| netif_device_attach(netdev); |
| adapter->dev_state = adapter->dev_pm_state; |
| return 0; |
| |
| } |
| |
| #endif |
| |
| /* Wait until no more pending transmits */ |
| void be_wait_nic_tx_cmplx_cmpl(struct be_net_object *pnob) |
| { |
| int i; |
| |
| /* Wait for 20us * 50000 (= 1s) and no more */ |
| i = 0; |
| while ((pnob->tx_q_tl != pnob->tx_q_hd) && (i < 50000)) { |
| ++i; |
| udelay(20); |
| } |
| |
| /* Check for no more pending transmits */ |
| if (i >= 50000) { |
| printk(KERN_WARNING |
| "Did not receive completions for all TX requests\n"); |
| } |
| } |
| |
| static struct pci_driver be_driver = { |
| .name = be_driver_name, |
| .id_table = be_device_id_table, |
| .probe = be_probe, |
| #ifdef CONFIG_PM |
| .suspend = be_suspend, |
| .resume = be_resume, |
| #endif |
| .remove = be_remove |
| }; |
| |
| /* |
| * Module init entry point. Registers our our device and return. |
| * Our probe will be called if the device is found. |
| */ |
| static int __init be_init_module(void) |
| { |
| int ret; |
| |
| if (rxbuf_size != 8192 && rxbuf_size != 4096 && rxbuf_size != 2048) { |
| printk(KERN_WARNING |
| "Unsupported receive buffer size (%d) requested\n", |
| rxbuf_size); |
| printk(KERN_WARNING |
| "Must be 2048, 4096 or 8192. Defaulting to 2048\n"); |
| rxbuf_size = 2048; |
| } |
| |
| ret = pci_register_driver(&be_driver); |
| |
| return ret; |
| } |
| |
| module_init(be_init_module); |
| |
| /* |
| * be_exit_module - Driver Exit Cleanup Routine |
| */ |
| static void __exit be_exit_module(void) |
| { |
| pci_unregister_driver(&be_driver); |
| } |
| |
| module_exit(be_exit_module); |