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:
@@ -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)));
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user