@@ -24,6 +24,17 @@ struct txgbe_mac_addr {
#define TXGBE_MAC_STATE_MODIFIED 0x2
#define TXGBE_MAC_STATE_IN_USE 0x4
+enum txgbe_state_t {
+ __TXGBE_TESTING,
+ __TXGBE_RESETTING,
+ __TXGBE_DOWN,
+ __TXGBE_HANGING,
+ __TXGBE_DISABLED,
+ __TXGBE_REMOVING,
+ __TXGBE_SERVICE_SCHED,
+ __TXGBE_SERVICE_INITED,
+};
+
/* board specific private data structure */
struct txgbe_adapter {
u8 __iomem *io_addr; /* Mainly for iounmap use */
@@ -31,6 +42,10 @@ struct txgbe_adapter {
struct net_device *netdev;
struct pci_dev *pdev;
+ unsigned long state;
+ struct timer_list service_timer;
+ struct work_struct service_task;
+
/* structs defined in txgbe_type.h */
struct txgbe_hw hw;
u16 msg_enable;
@@ -35,6 +35,8 @@ static const struct pci_device_id txgbe_pci_tbl[] = {
#define DEFAULT_DEBUG_LEVEL_SHIFT 3
+static struct workqueue_struct *txgbe_wq;
+
static void txgbe_check_minimum_link(struct txgbe_adapter *adapter)
{
struct pci_dev *pdev;
@@ -73,6 +75,50 @@ static int txgbe_enumerate_functions(struct txgbe_adapter *adapter)
return physfns;
}
+static void txgbe_service_event_schedule(struct txgbe_adapter *adapter)
+{
+ if (!test_bit(__TXGBE_DOWN, &adapter->state) &&
+ !test_bit(__TXGBE_REMOVING, &adapter->state) &&
+ !test_and_set_bit(__TXGBE_SERVICE_SCHED, &adapter->state))
+ queue_work(txgbe_wq, &adapter->service_task);
+}
+
+static void txgbe_service_event_complete(struct txgbe_adapter *adapter)
+{
+ if (WARN_ON(!test_bit(__TXGBE_SERVICE_SCHED, &adapter->state)))
+ return;
+
+ /* flush memory to make sure state is correct before next watchdog */
+ smp_mb__before_atomic();
+ clear_bit(__TXGBE_SERVICE_SCHED, &adapter->state);
+}
+
+static void txgbe_service_timer(struct timer_list *t)
+{
+ struct txgbe_adapter *adapter = from_timer(adapter, t, service_timer);
+ unsigned long next_event_offset;
+
+ next_event_offset = HZ * 2;
+
+ /* Reset the timer */
+ mod_timer(&adapter->service_timer, next_event_offset + jiffies);
+
+ txgbe_service_event_schedule(adapter);
+}
+
+/**
+ * txgbe_service_task - manages and runs subtasks
+ * @work: pointer to work_struct containing our data
+ **/
+static void txgbe_service_task(struct work_struct *work)
+{
+ struct txgbe_adapter *adapter = container_of(work,
+ struct txgbe_adapter,
+ service_task);
+
+ txgbe_service_event_complete(adapter);
+}
+
static void txgbe_sync_mac_table(struct txgbe_adapter *adapter)
{
struct txgbe_hw *hw = &adapter->hw;
@@ -190,6 +236,10 @@ static void txgbe_disable_device(struct txgbe_adapter *adapter)
struct net_device *netdev = adapter->netdev;
struct wx_hw *wxhw = &adapter->hw.wxhw;
+ /* signal that we are down to the interrupt handler */
+ if (test_and_set_bit(__TXGBE_DOWN, &adapter->state))
+ return; /* do nothing if already down */
+
wx_disable_pcie_master(wxhw);
/* disable receives */
wx_disable_rx(wxhw);
@@ -197,6 +247,8 @@ static void txgbe_disable_device(struct txgbe_adapter *adapter)
netif_carrier_off(netdev);
netif_tx_disable(netdev);
+ del_timer_sync(&adapter->service_timer);
+
if (wxhw->bus.func < 2)
wr32m(wxhw, TXGBE_MIS_PRB_CTL, TXGBE_MIS_PRB_CTL_LAN_UP(wxhw->bus.func), 0);
else
@@ -266,6 +318,8 @@ static int txgbe_sw_init(struct txgbe_adapter *adapter)
return -ENOMEM;
}
+ set_bit(__TXGBE_DOWN, &adapter->state);
+
return 0;
}
@@ -336,7 +390,8 @@ static void txgbe_dev_shutdown(struct pci_dev *pdev, bool *enable_wake)
wx_control_hw(wxhw, false);
- pci_disable_device(pdev);
+ if (!test_and_set_bit(__TXGBE_DISABLED, &adapter->state))
+ pci_disable_device(pdev);
}
static void txgbe_shutdown(struct pci_dev *pdev)
@@ -410,6 +465,7 @@ static int txgbe_probe(struct pci_dev *pdev,
struct txgbe_hw *hw = NULL;
struct wx_hw *wxhw = NULL;
struct net_device *netdev;
+ bool disable_dev = false;
int err, expected_gts;
u16 eeprom_verh = 0, eeprom_verl = 0, offset = 0;
@@ -468,6 +524,7 @@ static int txgbe_probe(struct pci_dev *pdev,
}
netdev->netdev_ops = &txgbe_netdev_ops;
+ netdev->watchdog_timeo = 5 * HZ;
/* setup the private structure */
err = txgbe_sw_init(adapter);
@@ -518,6 +575,11 @@ static int txgbe_probe(struct pci_dev *pdev,
eth_hw_addr_set(netdev, wxhw->mac.perm_addr);
txgbe_mac_set_default_filter(adapter, wxhw->mac.perm_addr);
+ timer_setup(&adapter->service_timer, txgbe_service_timer, 0);
+ INIT_WORK(&adapter->service_task, txgbe_service_task);
+ set_bit(__TXGBE_SERVICE_INITED, &adapter->state);
+ clear_bit(__TXGBE_SERVICE_SCHED, &adapter->state);
+
/* Save off EEPROM version number and Option Rom version which
* together make a unique identify for the eeprom
*/
@@ -599,11 +661,13 @@ static int txgbe_probe(struct pci_dev *pdev,
err_free_mac_table:
kfree(adapter->mac_table);
err_pci_release_regions:
+ disable_dev = !test_and_set_bit(__TXGBE_DISABLED, &adapter->state);
pci_disable_pcie_error_reporting(pdev);
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
err_pci_disable_dev:
- pci_disable_device(pdev);
+ if (!adapter || disable_dev)
+ pci_disable_device(pdev);
return err;
}
@@ -620,18 +684,25 @@ static void txgbe_remove(struct pci_dev *pdev)
{
struct txgbe_adapter *adapter = pci_get_drvdata(pdev);
struct net_device *netdev;
+ bool disable_dev;
netdev = adapter->netdev;
+
+ set_bit(__TXGBE_REMOVING, &adapter->state);
+ cancel_work_sync(&adapter->service_task);
+
unregister_netdev(netdev);
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
kfree(adapter->mac_table);
+ disable_dev = !test_and_set_bit(__TXGBE_DISABLED, &adapter->state);
pci_disable_pcie_error_reporting(pdev);
- pci_disable_device(pdev);
+ if (disable_dev)
+ pci_disable_device(pdev);
}
static struct pci_driver txgbe_driver = {
@@ -642,7 +713,41 @@ static struct pci_driver txgbe_driver = {
.shutdown = txgbe_shutdown,
};
-module_pci_driver(txgbe_driver);
+/**
+ * txgbe_init_module - Driver Registration Routine
+ *
+ * txgbe_init_module is the first routine called when the driver is
+ * loaded. All it does is register with the PCI subsystem.
+ **/
+static int __init txgbe_init_module(void)
+{
+ int ret;
+
+ txgbe_wq = create_singlethread_workqueue(txgbe_driver_name);
+ if (!txgbe_wq) {
+ pr_err("%s: Failed to create workqueue\n", txgbe_driver_name);
+ return -ENOMEM;
+ }
+
+ ret = pci_register_driver(&txgbe_driver);
+ return ret;
+}
+
+module_init(txgbe_init_module);
+
+/**
+ * txgbe_exit_module - Driver Exit Cleanup Routine
+ *
+ * txgbe_exit_module is called just before the driver is removed
+ * from memory.
+ **/
+static void __exit txgbe_exit_module(void)
+{
+ pci_unregister_driver(&txgbe_driver);
+ destroy_workqueue(txgbe_wq);
+}
+
+module_exit(txgbe_exit_module);
MODULE_DEVICE_TABLE(pci, txgbe_pci_tbl);
MODULE_AUTHOR("Beijing WangXun Technology Co., Ltd, <software@trustnetic.com>");