diff options
author | Dan Williams <dcbw@redhat.com> | 2010-03-11 13:20:43 -0800 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2010-03-11 13:20:43 -0800 |
commit | 749f9c0eb569b29772dde9561b9856e4f878d9ef (patch) | |
tree | 6f211060b7e7fa6e4cf9e17c2731f56c6fad5504 /src/mm-plugin-base.c | |
parent | c7739979ed4a8be97d03ab7ed0087d63dc218cf4 (diff) |
core: stop probing known-unusable ports early
Some ports we know we shouldn't use when we get certain responses
from them. Reading from these ports triggers kernel bugs (at least
on 2.6.31 and 2.6.32) relating to flow control in some drivers
(*cough* hso *cough*), so lets try not to aggravate the kernel too
much. This happens on Icera-based Option devices like the GI0322
(AT&T Quicksilver) for example.
(note: AFAICT this doesn't have any relation to the recent XON/XOFF
patch, since I get this problem without the XON/XOFF patch on both
2.6.31 and 2.6.32 as well)
---
BUG: sleeping function called from invalid context at kernel/mutex.c:94
in_atomic(): 1, irqs_disabled(): 1, pid: 9295, name: modem-manager
Pid: 9295, comm: modem-manager Not tainted 2.6.32.9-67.fc12.x86_64 #1
Call Trace:
<IRQ> [<ffffffff81045d41>] __might_sleep+0xed/0xef
[<ffffffff81454dd0>] mutex_lock+0x24/0x50
[<ffffffff8104811e>] ? enqueue_task_fair+0x2a/0x6d
[<ffffffff812af79f>] tty_throttle+0x1b/0x49
[<ffffffff812af0d9>] n_tty_receive_buf+0xdbb/0xe12
[<ffffffff810459fd>] ? task_rq_unlock+0x11/0x13
[<ffffffff81050c5c>] ? try_to_wake_up+0x2f3/0x305
[<ffffffff8110de0c>] ? __kmalloc+0x37/0x15e
[<ffffffff8110de42>] ? __kmalloc+0x6d/0x15e
[<ffffffff812b12c9>] flush_to_ldisc+0xf8/0x18d
[<ffffffff812b13ae>] tty_flip_buffer_push+0x50/0x61
[<ffffffffa040ccd5>] put_rxbuf_data+0xea/0x124 [hso]
[<ffffffffa040cd97>] put_rxbuf_data_and_resubmit_bulk_urb+0x21/0x6b [hso]
[<ffffffffa040d0b1>] hso_std_serial_read_bulk_callback+0x14d/0x15f [hso]
[<ffffffff8132edf7>] ? dma_unmap_single_attrs.clone.0+0x38/0x3a
[<ffffffff8132ef74>] usb_hcd_giveback_urb+0x91/0xc5
[<ffffffff813417c8>] ehci_urb_done+0x7b/0x90
[<ffffffff81050c5c>] ? try_to_wake_up+0x2f3/0x305
[<ffffffff81341b45>] qh_completions+0x368/0x4b9
[<ffffffff8103e7a0>] ? __wake_up_common+0x4e/0x84
[<ffffffff81343f70>] ehci_work+0x95/0x732
[<ffffffff81045b53>] ? __wake_up+0x44/0x4d
[<ffffffff81070490>] ? insert_work+0x8e/0x9b
[<ffffffff81345f01>] ehci_irq+0x2be/0x420
[<ffffffff8107071a>] ? __queue_work+0x3a/0x41
[<ffffffff81049e43>] ? resched_cpu+0x6e/0x77
[<ffffffff8107075d>] ? delayed_work_timer_fn+0x3c/0x3e
[<ffffffff810b0e44>] ? __rcu_process_callbacks+0x7d/0x28a
[<ffffffff8132e846>] usb_hcd_irq+0x3f/0x7b
[<ffffffff810acd61>] handle_IRQ_event+0x60/0x121
[<ffffffff810aeb8e>] handle_fasteoi_irq+0x8b/0xc7
[<ffffffff81014625>] handle_irq+0x8b/0x96
[<ffffffff81459c14>] do_IRQ+0x5c/0xbc
[<ffffffff81012693>] ret_from_intr+0x0/0x11
Diffstat (limited to 'src/mm-plugin-base.c')
-rw-r--r-- | src/mm-plugin-base.c | 55 |
1 files changed, 54 insertions, 1 deletions
diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c index 202eaac9..4b74feb7 100644 --- a/src/mm-plugin-base.c +++ b/src/mm-plugin-base.c @@ -77,6 +77,8 @@ typedef enum { PROBE_STATE_LAST } ProbeState; +static void probe_complete (MMPluginBaseSupportsTask *task); + /*****************************************************************************/ G_DEFINE_TYPE (MMPluginBaseSupportsTask, mm_plugin_base_supports_task, G_TYPE_OBJECT) @@ -91,6 +93,7 @@ typedef struct { guint open_id; guint32 open_tries; + guint full_id; MMSerialPort *probe_port; guint32 probed_caps; @@ -198,6 +201,11 @@ mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task, priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); g_return_if_fail (priv->callback != NULL); + if (priv->full_id) { + g_source_remove (priv->full_id); + priv->full_id = 0; + } + subsys = g_udev_device_get_subsystem (priv->port); name = g_udev_device_get_name (priv->port); @@ -251,11 +259,15 @@ supports_task_dispose (GObject *object) if (priv->open_id) g_source_remove (priv->open_id); + if (priv->full_id) + g_source_remove (priv->full_id); if (priv->probe_id) g_source_remove (priv->probe_id); - if (priv->probe_port) + if (priv->probe_port) { + mm_serial_port_close (priv->probe_port); g_object_unref (priv->probe_port); + } G_OBJECT_CLASS (mm_plugin_base_supports_task_parent_class)->dispose (object); } @@ -349,6 +361,44 @@ parse_cgmm (const char *buf) return 0; } +static const char *dq_strings[] = { + "option/faema_", "os_logids.h", NULL +}; + +static void +port_buffer_full (MMSerialPort *port, GString *buffer, gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (user_data); + const char **iter; + size_t iter_len; + int i; + + /* Check for an immediate disqualification response. There are some + * ports (Option Icera-based chipsets have them, as do Qualcomm Gobi + * devices before their firmware is loaded) that just shouldn't be + * probed if we get a certain response because we know they can't be + * used. Kernel bugs (at least with 2.6.31 and 2.6.32) also trigger port + * flow control kernel oopses if we read too much data for these ports. + */ + + for (iter = &dq_strings[0]; iter && *iter; iter++) { + /* Search in the response for the item; the response could have embedded + * nulls so we can't use memcmp() or strstr() on the whole response. + */ + iter_len = strlen (*iter); + for (i = 0; i < buffer->len - iter_len; i++) { + if (!memcmp (&buffer->str[i], *iter, iter_len)) { + /* Immediately close the port and complete probing */ + priv->probed_caps = 0; + mm_serial_port_close (priv->probe_port); + probe_complete (task); + return; + } + } + } +} + static gboolean emit_probe_result (gpointer user_data) { @@ -626,6 +676,9 @@ try_open (gpointer user_data) port = mm_plugin_base_supports_task_get_port (task); g_assert (port); + task_priv->full_id = g_signal_connect (task_priv->probe_port, "buffer-full", + G_CALLBACK (port_buffer_full), task); + g_debug ("(%s): probe requested by plugin '%s'", g_udev_device_get_name (port), mm_plugin_get_name (MM_PLUGIN (task_priv->plugin))); |