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
This commit is contained in:
Dan Williams
2010-03-11 13:20:43 -08:00
parent c7739979ed
commit 749f9c0eb5
3 changed files with 79 additions and 8 deletions

View File

@@ -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)));

View File

@@ -730,9 +730,13 @@ data_available (GIOChannel *source,
status = g_io_channel_read_chars (source, buf, SERIAL_BUF_SIZE, &bytes_read, &err);
if (status == G_IO_STATUS_ERROR) {
g_warning ("%s", err->message);
g_error_free (err);
err = NULL;
if (err && err->message)
g_warning ("%s", err->message);
g_clear_error (&err);
/* Serial port is closed; we're done */
if (priv->watch_id == 0)
break;
}
/* If no bytes read, just let g_io_channel wait for more data */
@@ -745,10 +749,10 @@ data_available (GIOChannel *source,
}
/* Make sure the string doesn't grow too long */
if (priv->response->len > SERIAL_BUF_SIZE) {
g_warning ("%s (%s): response buffer filled before repsonse received",
G_STRFUNC, mm_port_get_device (MM_PORT (self)));
g_string_erase (priv->response, 0, (SERIAL_BUF_SIZE / 2));
if (priv->response->len > SERIAL_BUF_SIZE) {
/* Notify listeners and then trim the buffer */
g_signal_emit_by_name (self, "buffer-full", priv->response);
g_string_erase (priv->response, 0, (SERIAL_BUF_SIZE / 2));
}
if (parse_response (self, priv->response, &err))
@@ -888,6 +892,7 @@ mm_serial_port_close (MMSerialPort *self)
if (priv->channel) {
g_source_remove (priv->watch_id);
priv->watch_id = 0;
g_io_channel_shutdown (priv->channel, TRUE, NULL);
g_io_channel_unref (priv->channel);
priv->channel = NULL;
@@ -1293,4 +1298,14 @@ mm_serial_port_class_init (MMSerialPortClass *klass)
"Send delay",
0, G_MAXUINT64, 0,
G_PARAM_READWRITE));
/* Signals */
g_signal_new ("buffer-full",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (MMSerialPortClass, buffer_full),
NULL, NULL,
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
}

View File

@@ -62,6 +62,9 @@ struct _MMSerialPort {
struct _MMSerialPortClass {
MMPortClass parent;
/* Signals */
void (*buffer_full) (MMSerialPort *port, const GString *buffer);
};
GType mm_serial_port_get_type (void);