diff options
Diffstat (limited to 'drivers/net/wimax/i2400m/driver.c')
| -rw-r--r-- | drivers/net/wimax/i2400m/driver.c | 264 |
1 files changed, 138 insertions, 126 deletions
diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index 96a615fe09d..9c78090e72f 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c @@ -69,30 +69,12 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/suspend.h> +#include <linux/slab.h> #define D_SUBMODULE driver #include "debug-levels.h" -int i2400m_idle_mode_disabled; /* 0 (idle mode enabled) by default */ -module_param_named(idle_mode_disabled, i2400m_idle_mode_disabled, int, 0644); -MODULE_PARM_DESC(idle_mode_disabled, - "If true, the device will not enable idle mode negotiation " - "with the base station (when connected) to save power."); - -int i2400m_rx_reorder_disabled; /* 0 (rx reorder enabled) by default */ -module_param_named(rx_reorder_disabled, i2400m_rx_reorder_disabled, int, 0644); -MODULE_PARM_DESC(rx_reorder_disabled, - "If true, RX reordering will be disabled."); - -int i2400m_power_save_disabled; /* 0 (power saving enabled) by default */ -module_param_named(power_save_disabled, i2400m_power_save_disabled, int, 0644); -MODULE_PARM_DESC(power_save_disabled, - "If true, the driver will not tell the device to enter " - "power saving mode when it reports it is ready for it. " - "False by default (so the device is told to do power " - "saving)."); - static char i2400m_debug_params[128]; module_param_string(debug, i2400m_debug_params, sizeof(i2400m_debug_params), 0644); @@ -110,54 +92,6 @@ MODULE_PARM_DESC(barkers, "signal; values are appended to a list--setting one value " "as zero cleans the existing list and starts a new one."); -static -struct i2400m_work *__i2400m_work_setup( - struct i2400m *i2400m, void (*fn)(struct work_struct *), - gfp_t gfp_flags, const void *pl, size_t pl_size) -{ - struct i2400m_work *iw; - - iw = kzalloc(sizeof(*iw) + pl_size, gfp_flags); - if (iw == NULL) - return NULL; - iw->i2400m = i2400m_get(i2400m); - iw->pl_size = pl_size; - memcpy(iw->pl, pl, pl_size); - INIT_WORK(&iw->ws, fn); - return iw; -} - - -/* - * Schedule i2400m's specific work on the system's queue. - * - * Used for a few cases where we really need it; otherwise, identical - * to i2400m_queue_work(). - * - * Returns < 0 errno code on error, 1 if ok. - * - * If it returns zero, something really bad happened, as it means the - * works struct was already queued, but we have just allocated it, so - * it should not happen. - */ -int i2400m_schedule_work(struct i2400m *i2400m, - void (*fn)(struct work_struct *), gfp_t gfp_flags, - const void *pl, size_t pl_size) -{ - int result; - struct i2400m_work *iw; - - result = -ENOMEM; - iw = __i2400m_work_setup(i2400m, fn, gfp_flags, pl, pl_size); - if (iw != NULL) { - result = schedule_work(&iw->ws); - if (WARN_ON(result == 0)) - result = -ENXIO; - } - return result; -} - - /* * WiMAX stack operation: relay a message from user space * @@ -288,7 +222,6 @@ int i2400m_check_mac_addr(struct i2400m *i2400m) struct sk_buff *skb; const struct i2400m_tlv_detailed_device_info *ddi; struct net_device *net_dev = i2400m->wimax_dev.net_dev; - const unsigned char zeromac[ETH_ALEN] = { 0 }; d_fnstart(3, dev, "(i2400m %p)\n", i2400m); skb = i2400m_get_device_info(i2400m); @@ -298,28 +231,19 @@ int i2400m_check_mac_addr(struct i2400m *i2400m) result); goto error; } - /* Extract MAC addresss */ + /* Extract MAC address */ ddi = (void *) skb->data; BUILD_BUG_ON(ETH_ALEN != sizeof(ddi->mac_address)); - d_printf(2, dev, "GET DEVICE INFO: mac addr " - "%02x:%02x:%02x:%02x:%02x:%02x\n", - ddi->mac_address[0], ddi->mac_address[1], - ddi->mac_address[2], ddi->mac_address[3], - ddi->mac_address[4], ddi->mac_address[5]); + d_printf(2, dev, "GET DEVICE INFO: mac addr %pM\n", + ddi->mac_address); if (!memcmp(net_dev->perm_addr, ddi->mac_address, sizeof(ddi->mac_address))) goto ok; dev_warn(dev, "warning: device reports a different MAC address " "to that of boot mode's\n"); - dev_warn(dev, "device reports %02x:%02x:%02x:%02x:%02x:%02x\n", - ddi->mac_address[0], ddi->mac_address[1], - ddi->mac_address[2], ddi->mac_address[3], - ddi->mac_address[4], ddi->mac_address[5]); - dev_warn(dev, "boot mode reported %02x:%02x:%02x:%02x:%02x:%02x\n", - net_dev->perm_addr[0], net_dev->perm_addr[1], - net_dev->perm_addr[2], net_dev->perm_addr[3], - net_dev->perm_addr[4], net_dev->perm_addr[5]); - if (!memcmp(zeromac, ddi->mac_address, sizeof(zeromac))) + dev_warn(dev, "device reports %pM\n", ddi->mac_address); + dev_warn(dev, "boot mode reported %pM\n", net_dev->perm_addr); + if (is_zero_ether_addr(ddi->mac_address)) dev_err(dev, "device reports an invalid MAC address, " "not updating\n"); else { @@ -403,6 +327,16 @@ retry: result = i2400m_dev_initialize(i2400m); if (result < 0) goto error_dev_initialize; + + /* We don't want any additional unwanted error recovery triggered + * from any other context so if anything went wrong before we come + * here, let's keep i2400m->error_recovery untouched and leave it to + * dev_reset_handle(). See dev_reset_handle(). */ + + atomic_dec(&i2400m->error_recovery); + /* Every thing works so far, ok, now we are ready to + * take error recovery if it's required. */ + /* At this point, reports will come for the device and set it * to the right state if it is different than UNINITIALIZED */ d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n", @@ -411,10 +345,10 @@ retry: error_dev_initialize: error_check_mac_addr: +error_fw_check: i2400m->ready = 0; wmb(); /* see i2400m->ready's documentation */ flush_workqueue(i2400m->work_queue); -error_fw_check: if (i2400m->bus_dev_stop) i2400m->bus_dev_stop(i2400m); error_bus_dev_start: @@ -444,7 +378,8 @@ int i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri bm_flags) result = __i2400m_dev_start(i2400m, bm_flags); if (result >= 0) { i2400m->updown = 1; - wmb(); /* see i2400m->updown's documentation */ + i2400m->alive = 1; + wmb();/* see i2400m->updown and i2400m->alive's doc */ } } mutex_unlock(&i2400m->init_mutex); @@ -505,7 +440,8 @@ void i2400m_dev_stop(struct i2400m *i2400m) if (i2400m->updown) { __i2400m_dev_stop(i2400m); i2400m->updown = 0; - wmb(); /* see i2400m->updown's documentation */ + i2400m->alive = 0; + wmb(); /* see i2400m->updown and i2400m->alive's doc */ } mutex_unlock(&i2400m->init_mutex); } @@ -564,26 +500,23 @@ int i2400m_pm_notifier(struct notifier_block *notifier, */ int i2400m_pre_reset(struct i2400m *i2400m) { - int result; struct device *dev = i2400m_dev(i2400m); d_fnstart(3, dev, "(i2400m %p)\n", i2400m); d_printf(1, dev, "pre-reset shut down\n"); - result = 0; mutex_lock(&i2400m->init_mutex); if (i2400m->updown) { netif_tx_disable(i2400m->wimax_dev.net_dev); __i2400m_dev_stop(i2400m); - result = 0; /* down't set updown to zero -- this way * post_reset can restore properly */ } mutex_unlock(&i2400m->init_mutex); if (i2400m->bus_release) i2400m->bus_release(i2400m); - d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); - return result; + d_fnend(3, dev, "(i2400m %p) = 0\n", i2400m); + return 0; } EXPORT_SYMBOL_GPL(i2400m_pre_reset); @@ -625,12 +558,12 @@ int i2400m_post_reset(struct i2400m *i2400m) error_dev_start: if (i2400m->bus_release) i2400m->bus_release(i2400m); -error_bus_setup: /* even if the device was up, it could not be recovered, so we * mark it as down. */ i2400m->updown = 0; wmb(); /* see i2400m->updown's documentation */ mutex_unlock(&i2400m->init_mutex); +error_bus_setup: d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); return result; } @@ -663,20 +596,17 @@ EXPORT_SYMBOL_GPL(i2400m_post_reset); static void __i2400m_dev_reset_handle(struct work_struct *ws) { - int result; - struct i2400m_work *iw = container_of(ws, struct i2400m_work, ws); - const char *reason; - struct i2400m *i2400m = iw->i2400m; + struct i2400m *i2400m = container_of(ws, struct i2400m, reset_ws); + const char *reason = i2400m->reset_reason; struct device *dev = i2400m_dev(i2400m); struct i2400m_reset_ctx *ctx = i2400m->reset_ctx; - - if (WARN_ON(iw->pl_size != sizeof(reason))) - reason = "SW BUG: reason n/a"; - else - memcpy(&reason, iw->pl, sizeof(reason)); + int result; d_fnstart(3, dev, "(ws %p i2400m %p reason %s)\n", ws, i2400m, reason); + i2400m->boot_mode = 1; + wmb(); /* Make sure i2400m_msg_to_dev() sees boot_mode */ + result = 0; if (mutex_trylock(&i2400m->init_mutex) == 0) { /* We are still in i2400m_dev_start() [let it fail] or @@ -687,39 +617,66 @@ void __i2400m_dev_reset_handle(struct work_struct *ws) complete(&i2400m->msg_completion); goto out; } - if (i2400m->updown == 0) { - dev_info(dev, "%s: device is down, doing nothing\n", reason); - goto out_unlock; - } + dev_err(dev, "%s: reinitializing driver\n", reason); - __i2400m_dev_stop(i2400m); - result = __i2400m_dev_start(i2400m, - I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT); - if (result < 0) { + rmb(); + if (i2400m->updown) { + __i2400m_dev_stop(i2400m); i2400m->updown = 0; wmb(); /* see i2400m->updown's documentation */ - dev_err(dev, "%s: cannot start the device: %d\n", - reason, result); - result = -EUCLEAN; } -out_unlock: + + if (i2400m->alive) { + result = __i2400m_dev_start(i2400m, + I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT); + if (result < 0) { + dev_err(dev, "%s: cannot start the device: %d\n", + reason, result); + result = -EUCLEAN; + if (atomic_read(&i2400m->bus_reset_retries) + >= I2400M_BUS_RESET_RETRIES) { + result = -ENODEV; + dev_err(dev, "tried too many times to " + "reset the device, giving up\n"); + } + } + } + if (i2400m->reset_ctx) { ctx->result = result; complete(&ctx->completion); } mutex_unlock(&i2400m->init_mutex); if (result == -EUCLEAN) { + /* + * We come here because the reset during operational mode + * wasn't successfully done and need to proceed to a bus + * reset. For the dev_reset_handle() to be able to handle + * the reset event later properly, we restore boot_mode back + * to the state before previous reset. ie: just like we are + * issuing the bus reset for the first time + */ + i2400m->boot_mode = 0; + wmb(); + + atomic_inc(&i2400m->bus_reset_retries); /* ops, need to clean up [w/ init_mutex not held] */ result = i2400m_reset(i2400m, I2400M_RT_BUS); if (result >= 0) result = -ENODEV; + } else { + rmb(); + if (i2400m->alive) { + /* great, we expect the device state up and + * dev_start() actually brings the device state up */ + i2400m->updown = 1; + wmb(); + atomic_set(&i2400m->bus_reset_retries, 0); + } } out: - i2400m_put(i2400m); - kfree(iw); d_fnend(3, dev, "(ws %p i2400m %p reason %s) = void\n", ws, i2400m, reason); - return; } @@ -737,19 +694,63 @@ out: */ int i2400m_dev_reset_handle(struct i2400m *i2400m, const char *reason) { - i2400m->boot_mode = 1; - wmb(); /* Make sure i2400m_msg_to_dev() sees boot_mode */ - return i2400m_schedule_work(i2400m, __i2400m_dev_reset_handle, - GFP_ATOMIC, &reason, sizeof(reason)); + i2400m->reset_reason = reason; + return schedule_work(&i2400m->reset_ws); } EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle); + /* + * The actual work of error recovery. + * + * The current implementation of error recovery is to trigger a bus reset. + */ +static +void __i2400m_error_recovery(struct work_struct *ws) +{ + struct i2400m *i2400m = container_of(ws, struct i2400m, recovery_ws); + + i2400m_reset(i2400m, I2400M_RT_BUS); +} + +/* + * Schedule a work struct for error recovery. + * + * The intention of error recovery is to bring back the device to some + * known state whenever TX sees -110 (-ETIMEOUT) on copying the data to + * the device. The TX failure could mean a device bus stuck, so the current + * error recovery implementation is to trigger a bus reset to the device + * and hopefully it can bring back the device. + * + * The actual work of error recovery has to be in a thread context because + * it is kicked off in the TX thread (i2400ms->tx_workqueue) which is to be + * destroyed by the error recovery mechanism (currently a bus reset). + * + * Also, there may be already a queue of TX works that all hit + * the -ETIMEOUT error condition because the device is stuck already. + * Since bus reset is used as the error recovery mechanism and we don't + * want consecutive bus resets simply because the multiple TX works + * in the queue all hit the same device erratum, the flag "error_recovery" + * is introduced for preventing unwanted consecutive bus resets. + * + * Error recovery shall only be invoked again if previous one was completed. + * The flag error_recovery is set when error recovery mechanism is scheduled, + * and is checked when we need to schedule another error recovery. If it is + * in place already, then we shouldn't schedule another one. + */ +void i2400m_error_recovery(struct i2400m *i2400m) +{ + if (atomic_add_return(1, &i2400m->error_recovery) == 1) + schedule_work(&i2400m->recovery_ws); + else + atomic_dec(&i2400m->error_recovery); +} +EXPORT_SYMBOL_GPL(i2400m_error_recovery); + /* * Alloc the command and ack buffers for boot mode * - * Get the buffers needed to deal with boot mode messages. These - * buffers need to be allocated before the sdio recieve irq is setup. + * Get the buffers needed to deal with boot mode messages. */ static int i2400m_bm_buf_alloc(struct i2400m *i2400m) @@ -811,6 +812,17 @@ void i2400m_init(struct i2400m *i2400m) mutex_init(&i2400m->init_mutex); /* wake_tx_ws is initialized in i2400m_tx_setup() */ + + INIT_WORK(&i2400m->reset_ws, __i2400m_dev_reset_handle); + INIT_WORK(&i2400m->recovery_ws, __i2400m_error_recovery); + + atomic_set(&i2400m->bus_reset_retries, 0); + + i2400m->alive = 0; + + /* initialize error_recovery to 1 for denoting we + * are not yet ready to take any error recovery */ + atomic_set(&i2400m->error_recovery, 1); } EXPORT_SYMBOL_GPL(i2400m_init); @@ -880,7 +892,7 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) result = i2400m_read_mac_addr(i2400m); if (result < 0) goto error_read_mac_addr; - random_ether_addr(i2400m->src_mac_addr); + eth_random_addr(i2400m->src_mac_addr); i2400m->pm_notifier.notifier_call = i2400m_pm_notifier; register_pm_notifier(&i2400m->pm_notifier); @@ -958,6 +970,9 @@ void i2400m_release(struct i2400m *i2400m) i2400m_dev_stop(i2400m); + cancel_work_sync(&i2400m->reset_ws); + cancel_work_sync(&i2400m->recovery_ws); + i2400m_debugfs_rm(i2400m); sysfs_remove_group(&i2400m->wimax_dev.net_dev->dev.kobj, &i2400m_dev_attr_group); @@ -1001,10 +1016,7 @@ module_init(i2400m_driver_init); static void __exit i2400m_driver_exit(void) { - /* for scheds i2400m_dev_reset_handle() */ - flush_scheduled_work(); i2400m_barker_db_exit(); - return; } module_exit(i2400m_driver_exit); |
