Commit b9ee9501 authored by Inaky Perez-Gonzalez's avatar Inaky Perez-Gonzalez

wimax/i2400m: fix deadlock: don't do BUS reset under i2400m->init_mutex

Since the addition of the pre/post reset handlers, it became clear
that we cannot do a I2400M-RT-BUS type reset while holding the
init_mutex, as in the case of USB, it will deadlock when trying to
call i2400m_pre_reset().

Thus, the following changes:

 - clarify the fact that calling bus_reset() w/ I2400M_RT_BUS while
   holding init_mutex is a no-no.

 - i2400m_dev_reset_handle() will do a BUS reset to recover a gone
   device after unlocking init_mutex.

 - in the USB reset implementation, when cold and warm reset fails,
   fallback to QUEUING a usb reset, not executing a USB reset, so it
   happens from another context and does not deadlock.
Signed-off-by: default avatarInaky Perez-Gonzalez <inaky@linux.intel.com>
parent 5eeae35b
...@@ -765,9 +765,7 @@ void __i2400m_dev_reset_handle(struct work_struct *ws) ...@@ -765,9 +765,7 @@ void __i2400m_dev_reset_handle(struct work_struct *ws)
wmb(); /* see i2400m->updown's documentation */ wmb(); /* see i2400m->updown's documentation */
dev_err(dev, "%s: cannot start the device: %d\n", dev_err(dev, "%s: cannot start the device: %d\n",
reason, result); reason, result);
result = i2400m->bus_reset(i2400m, I2400M_RT_BUS); result = -EUCLEAN;
if (result >= 0)
result = -ENODEV;
} }
out_unlock: out_unlock:
if (i2400m->reset_ctx) { if (i2400m->reset_ctx) {
...@@ -775,6 +773,12 @@ void __i2400m_dev_reset_handle(struct work_struct *ws) ...@@ -775,6 +773,12 @@ void __i2400m_dev_reset_handle(struct work_struct *ws)
complete(&ctx->completion); complete(&ctx->completion);
} }
mutex_unlock(&i2400m->init_mutex); mutex_unlock(&i2400m->init_mutex);
if (result == -EUCLEAN) {
/* ops, need to clean up [w/ init_mutex not held] */
result = i2400m->bus_reset(i2400m, I2400M_RT_BUS);
if (result >= 0)
result = -ENODEV;
}
out: out:
i2400m_put(i2400m); i2400m_put(i2400m);
kfree(iw); kfree(iw);
......
...@@ -281,6 +281,9 @@ struct i2400m_barker_db; ...@@ -281,6 +281,9 @@ struct i2400m_barker_db;
* process, so it cannot rely on common infrastructure being laid * process, so it cannot rely on common infrastructure being laid
* out. * out.
* *
* IMPORTANT: don't call reset on RT_BUS with i2400m->init_mutex
* held, as the .pre/.post reset handlers will deadlock.
*
* @bus_bm_retries: [fill] How many times shall a firmware upload / * @bus_bm_retries: [fill] How many times shall a firmware upload /
* device initialization be retried? Different models of the same * device initialization be retried? Different models of the same
* device might need different values, hence it is set by the * device might need different values, hence it is set by the
......
...@@ -254,7 +254,6 @@ int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt) ...@@ -254,7 +254,6 @@ int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
sizeof(i2400m_COLD_BOOT_BARKER), sizeof(i2400m_COLD_BOOT_BARKER),
i2400mu->endpoint_cfg.reset_cold); i2400mu->endpoint_cfg.reset_cold);
else if (rt == I2400M_RT_BUS) { else if (rt == I2400M_RT_BUS) {
do_bus_reset:
result = usb_reset_device(i2400mu->usb_dev); result = usb_reset_device(i2400mu->usb_dev);
switch (result) { switch (result) {
case 0: case 0:
...@@ -262,7 +261,7 @@ int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt) ...@@ -262,7 +261,7 @@ int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
case -ENODEV: case -ENODEV:
case -ENOENT: case -ENOENT:
case -ESHUTDOWN: case -ESHUTDOWN:
result = rt == I2400M_RT_WARM ? -ENODEV : 0; result = 0;
break; /* We assume the device is disconnected */ break; /* We assume the device is disconnected */
default: default:
dev_err(dev, "USB reset failed (%d), giving up!\n", dev_err(dev, "USB reset failed (%d), giving up!\n",
...@@ -275,10 +274,17 @@ int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt) ...@@ -275,10 +274,17 @@ int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
if (result < 0 if (result < 0
&& result != -EINVAL /* device is gone */ && result != -EINVAL /* device is gone */
&& rt != I2400M_RT_BUS) { && rt != I2400M_RT_BUS) {
/*
* Things failed -- resort to lower level reset, that
* we queue in another context; the reason for this is
* that the pre and post reset functionality requires
* the i2400m->init_mutex; RT_WARM and RT_COLD can
* come from areas where i2400m->init_mutex is taken.
*/
dev_err(dev, "%s reset failed (%d); trying USB reset\n", dev_err(dev, "%s reset failed (%d); trying USB reset\n",
rt == I2400M_RT_WARM ? "warm" : "cold", result); rt == I2400M_RT_WARM ? "warm" : "cold", result);
rt = I2400M_RT_BUS; usb_queue_reset_device(i2400mu->usb_iface);
goto do_bus_reset; result = -ENODEV;
} }
d_fnend(3, dev, "(i2400m %p rt %u) = %d\n", i2400m, rt, result); d_fnend(3, dev, "(i2400m %p rt %u) = %d\n", i2400m, rt, result);
return result; return result;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment