diff options
Diffstat (limited to 'src/mm-port-serial.c')
-rw-r--r-- | src/mm-port-serial.c | 1025 |
1 files changed, 560 insertions, 465 deletions
diff --git a/src/mm-port-serial.c b/src/mm-port-serial.c index 3af1b8e2..8b6e34ec 100644 --- a/src/mm-port-serial.c +++ b/src/mm-port-serial.c @@ -34,7 +34,8 @@ #include <mm-errors-types.h> #include "mm-port-serial.h" -#include "mm-log.h" +#include "mm-log-object.h" +#include "mm-helper-enums-types.h" static gboolean port_serial_queue_process (gpointer data); static void port_serial_schedule_queue_process (MMPortSerial *self, @@ -53,10 +54,10 @@ enum { PROP_BITS, PROP_PARITY, PROP_STOPBITS, + PROP_FLOW_CONTROL, PROP_SEND_DELAY, PROP_FD, PROP_SPEW_CONTROL, - PROP_RTS_CTS, PROP_FLASH_OK, LAST_PROP @@ -90,15 +91,14 @@ struct _MMPortSerialPrivate { GSocket *socket; GSource *socket_source; - struct termios old_t; guint baud; guint bits; char parity; guint stopbits; + MMFlowControl flow_control; guint64 send_delay; gboolean spew_control; - gboolean rts_cts; gboolean flash_ok; guint queue_id; @@ -111,8 +111,8 @@ struct _MMPortSerialPrivate { guint connected_id; - gpointer flash_ctx; - gpointer reopen_ctx; + GTask *flash_task; + GTask *reopen_task; }; /*****************************************************************************/ @@ -163,6 +163,7 @@ mm_port_serial_command (MMPortSerial *self, GByteArray *command, guint32 timeout_seconds, gboolean allow_cached, + gboolean run_next, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) @@ -203,7 +204,12 @@ mm_port_serial_command (MMPortSerial *self, if (!allow_cached) port_serial_set_cached_reply (self, ctx->command, NULL); - g_queue_push_tail (self->priv->queue, ctx); + /* If requested to run next, push to the head of the queue so that it really is + * the next one sent */ + if (run_next) + g_queue_push_head (self->priv->queue, ctx); + else + g_queue_push_tail (self->priv->queue, ctx); if (g_queue_get_length (self->priv->queue) == 1) port_serial_schedule_queue_process (self, 0); @@ -211,95 +217,13 @@ mm_port_serial_command (MMPortSerial *self, /*****************************************************************************/ -#if 0 -static const char * -baud_to_string (int baud) -{ - const char *speed = NULL; - - switch (baud) { - case B0: - speed = "0"; - break; - case B50: - speed = "50"; - break; - case B75: - speed = "75"; - break; - case B110: - speed = "110"; - break; - case B150: - speed = "150"; - break; - case B300: - speed = "300"; - break; - case B600: - speed = "600"; - break; - case B1200: - speed = "1200"; - break; - case B2400: - speed = "2400"; - break; - case B4800: - speed = "4800"; - break; - case B9600: - speed = "9600"; - break; - case B19200: - speed = "19200"; - break; - case B38400: - speed = "38400"; - break; - case B57600: - speed = "57600"; - break; - case B115200: - speed = "115200"; - break; - case B460800: - speed = "460800"; - break; - default: - break; - } - - return speed; -} - -void -mm_port_serial_print_config (MMPortSerial *port, - const char *detail) -{ - struct termios stbuf; - int err; - - err = tcgetattr (self->priv->fd, &stbuf); - if (err) { - mm_warn ("*** %s (%s): (%s) tcgetattr() error %d", - __func__, detail, mm_port_get_device (MM_PORT (port)), errno); - return; - } - - mm_info ("(%s): (%s) baud rate: %d (%s)", - detail, mm_port_get_device (MM_PORT (port)), - stbuf.c_cflag & CBAUD, - baud_to_string (stbuf.c_cflag & CBAUD)); -} -#endif - -static int -parse_baudrate (guint i) +static gboolean +parse_baudrate (guint baudrate_num, + guint *out_baudrate_speed) { - int speed; + guint speed; - switch (i) { + switch (baudrate_num) { case 0: speed = B0; break; @@ -345,21 +269,28 @@ parse_baudrate (guint i) case 115200: speed = B115200; break; + case 230400: + speed = B230400; + break; case 460800: speed = B460800; break; + case 921600: + speed = B921600; + break; default: - mm_warn ("Invalid baudrate '%d'", i); - speed = B9600; + return FALSE; } - return speed; + if (out_baudrate_speed) + *out_baudrate_speed = speed; + return TRUE; } static int parse_bits (guint i) { - int bits; + int bits = -1; switch (i) { case 5: @@ -375,8 +306,7 @@ parse_bits (guint i) bits = CS8; break; default: - mm_warn ("Invalid bits (%d). Valid values are 5, 6, 7, 8.", i); - bits = CS8; + g_assert_not_reached (); } return bits; @@ -385,7 +315,7 @@ parse_bits (guint i) static int parse_parity (char c) { - int parity; + int parity = -1; switch (c) { case 'n': @@ -401,8 +331,7 @@ parse_parity (char c) parity = PARENB | PARODD; break; default: - mm_warn ("Invalid parity (%c). Valid values are n, e, o", c); - parity = 0; + g_assert_not_reached (); } return parity; @@ -411,7 +340,7 @@ parse_parity (char c) static int parse_stopbits (guint i) { - int stopbits; + int stopbits = -1; switch (i) { case 1: @@ -421,54 +350,145 @@ parse_stopbits (guint i) stopbits = CSTOPB; break; default: - mm_warn ("Invalid stop bits (%d). Valid values are 1 and 2)", i); - stopbits = 0; + g_assert_not_reached (); } return stopbits; } static gboolean +internal_tcsetattr (MMPortSerial *self, + gint fd, + const struct termios *options, + GError **error) +{ + guint count; + struct termios other; + +#define MAX_TCSETATTR_RETRIES 4 + + for (count = 0; count < MAX_TCSETATTR_RETRIES; count++) { + /* try to set the new port attributes */ + errno = 0; + if (tcsetattr (fd, TCSANOW, options) == 0) + break; + + /* hard error if not EAGAIN */ + if (errno != EAGAIN) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't set serial port attributes: %s", g_strerror (errno)); + return FALSE; + } + + /* try a few times if EAGAIN */ + g_usleep (100000); + } + + /* too many retries? */ + if (count == MAX_TCSETATTR_RETRIES) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't set serial port attributes: too many retries (%u)", count); + return FALSE; + } + + /* tcsetattr() returns 0 if any of the requested attributes could be set, + * so we should double-check that all were set and log if not. Just with + * debug level, as we're ignoring this issue all together anyway. + */ + memset (&other, 0, sizeof (struct termios)); + errno = 0; + if (tcgetattr (fd, &other) != 0) + mm_obj_dbg (self, "couldn't get serial port attributes after setting them: %s", g_strerror (errno)); + else if (memcmp (options, &other, sizeof (struct termios)) != 0) + mm_obj_dbg (self, "port attributes not fully set"); + +#undef MAX_TCSETATTR_RETRIES + + return TRUE; +} + +static gboolean +set_flow_control_termios (MMPortSerial *self, + MMFlowControl flow_control, + struct termios *options) +{ + gboolean had_xon_xoff; + gboolean had_rts_cts; + tcflag_t iflag_orig, cflag_orig; + + iflag_orig = options->c_iflag; + cflag_orig = options->c_cflag; + + had_xon_xoff = !!(options->c_iflag & (IXON | IXOFF)); + options->c_iflag &= ~(IXON | IXOFF | IXANY); + + had_rts_cts = !!(options->c_cflag & (CRTSCTS)); + options->c_cflag &= ~(CRTSCTS); + + /* setup the requested flags */ + switch (flow_control) { + case MM_FLOW_CONTROL_XON_XOFF: + mm_obj_dbg (self, "enabling XON/XOFF flow control"); + options->c_iflag |= (IXON | IXOFF | IXANY); + break; + case MM_FLOW_CONTROL_RTS_CTS: + mm_obj_dbg (self, "enabling RTS/CTS flow control"); + options->c_cflag |= (CRTSCTS); + break; + case MM_FLOW_CONTROL_NONE: + case MM_FLOW_CONTROL_UNKNOWN: + if (had_xon_xoff) + mm_obj_dbg (self, "disabling XON/XOFF flow control"); + if (had_rts_cts) + mm_obj_dbg (self, "disabling RTS/CTS flow control"); + break; + default: + g_assert_not_reached (); + } + + return iflag_orig != options->c_iflag || cflag_orig != options->c_cflag; +} + +static gboolean real_config_fd (MMPortSerial *self, int fd, GError **error) { - struct termios stbuf, other; - int speed; - int bits; - int parity; - int stopbits; + struct termios stbuf; + guint speed; + gint bits; + gint parity; + gint stopbits; /* No setup if not a tty */ if (mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_TTY) return TRUE; - speed = parse_baudrate (self->priv->baud); + mm_obj_dbg (self, "setting up baudrate: %u", self->priv->baud); + if (!parse_baudrate (self->priv->baud, &speed) || speed == B0) { + mm_obj_warn (self, "baudrate invalid: %u; defaulting to 57600", self->priv->baud); + speed = B57600; + } + bits = parse_bits (self->priv->bits); parity = parse_parity (self->priv->parity); stopbits = parse_stopbits (self->priv->stopbits); memset (&stbuf, 0, sizeof (struct termios)); - if (tcgetattr (fd, &stbuf) != 0) { - mm_warn ("(%s): tcgetattr() error: %d", - mm_port_get_device (MM_PORT (self)), - errno); - } + if (tcgetattr (fd, &stbuf) != 0) + mm_obj_warn (self, "error getting serial port attributes: %s", g_strerror (errno)); - stbuf.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY ); + stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB | PARODD | CRTSCTS); + stbuf.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXOFF | IXANY ); stbuf.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET); - stbuf.c_lflag &= ~(ICANON | XCASE | ECHO | ECHOE | ECHONL); - stbuf.c_lflag &= ~(ECHO | ECHOE); + stbuf.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL); stbuf.c_cc[VMIN] = 1; stbuf.c_cc[VTIME] = 0; stbuf.c_cc[VEOF] = 1; - /* Use software handshaking and ignore parity/framing errors */ - stbuf.c_iflag |= (IXON | IXOFF | IXANY | IGNPAR); + /* Ignore parity/framing errors */ + stbuf.c_iflag |= IGNPAR; - /* Set up port speed and serial attributes; also ignore modem control - * lines since most drivers don't implement RTS/CTS anyway. - */ - stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB | CRTSCTS); - stbuf.c_cflag |= (bits | CREAD | 0 | parity | stopbits | CLOCAL); + /* Set up port speed and serial attributes and enable receiver in local mode */ + stbuf.c_cflag |= (bits | parity | stopbits | CLOCAL | CREAD); errno = 0; if (cfsetispeed (&stbuf, speed) != 0) { @@ -490,36 +510,25 @@ real_config_fd (MMPortSerial *self, int fd, GError **error) return FALSE; } - if (tcsetattr (fd, TCSANOW, &stbuf) < 0) { - g_set_error (error, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "%s: failed to set serial port attributes; errno %d", - __func__, errno); - return FALSE; - } + if (self->priv->flow_control != MM_FLOW_CONTROL_UNKNOWN) { + gchar *str; - /* tcsetattr() returns 0 if any of the requested attributes could be set, - * so we should double-check that all were set and log a warning if not. - */ - memset (&other, 0, sizeof (struct termios)); - errno = 0; - if (tcgetattr (fd, &other) != 0) { - mm_warn ("(%s): tcgetattr() error: %d", - mm_port_get_device (MM_PORT (self)), - errno); - } + str = mm_flow_control_build_string_from_mask (self->priv->flow_control); + mm_obj_dbg (self, "flow control explicitly requested for device is: %s", str ? str : "unknown"); + g_free (str); + } else + mm_obj_dbg (self, "no flow control explicitly requested for device"); - if (memcmp (&stbuf, &other, sizeof (other)) != 0) { - mm_warn ("(%s): port attributes not fully set", - mm_port_get_device (MM_PORT (self))); - } + set_flow_control_termios (self, self->priv->flow_control, &stbuf); - return TRUE; + return internal_tcsetattr (self, fd, &stbuf, error); } static void -serial_debug (MMPortSerial *self, const char *prefix, const char *buf, gsize len) +serial_debug (MMPortSerial *self, + const gchar *prefix, + const gchar *buf, + gsize len) { g_return_if_fail (len > 0); @@ -551,7 +560,7 @@ port_serial_process_command (MMPortSerial *self, /* Only print command the first time */ if (ctx->started == FALSE) { ctx->started = TRUE; - serial_debug (self, "-->", (const char *) ctx->command->data, ctx->command->len); + serial_debug (self, "-->", (const gchar *) ctx->command->data, ctx->command->len); } if (self->priv->send_delay == 0 || mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_TTY) { @@ -585,7 +594,8 @@ port_serial_process_command (MMPortSerial *self, ctx->idx += written; break; } - /* If written == 0, treat as EAGAIN, so fall down */ + /* If written == 0 treat as EAGAIN */ + /* Fall through */ case G_IO_STATUS_AGAIN: /* We're in a non-blocking channel and therefore we're up to receive @@ -597,10 +607,13 @@ port_serial_process_command (MMPortSerial *self, g_signal_emit (self, signals[TIMED_OUT], 0, self->priv->n_consecutive_timeouts); g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, - "Sending command failed: '%s'", strerror (errno)); + "Sending command failed: '%s'", g_strerror (errno)); return FALSE; } break; + + default: + g_assert_not_reached (); } } /* Socket based setup */ @@ -629,14 +642,14 @@ port_serial_process_command (MMPortSerial *self, self->priv->n_consecutive_timeouts++; g_signal_emit (self, signals[TIMED_OUT], 0, self->priv->n_consecutive_timeouts); g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, - "Sending command failed: '%s'", strerror (errno)); + "Sending command failed: '%s'", g_strerror (errno)); return FALSE; } /* Just keep on, will retry... */ written = 0; } else - written = bytes_sent; + written = bytes_sent; ctx->idx += written; } else @@ -696,9 +709,11 @@ port_serial_schedule_queue_process (MMPortSerial *self, guint timeout_ms) static void port_serial_got_response (MMPortSerial *self, + GByteArray *parsed_response, const GError *error) { - CommandContext *ctx; + /* Either one or the other, not both */ + g_assert ((parsed_response && !error) || (!parsed_response && error)); if (self->priv->timeout_id) { g_source_remove (self->priv->timeout_id); @@ -714,28 +729,36 @@ port_serial_got_response (MMPortSerial *self, g_clear_object (&self->priv->cancellable); - ctx = (CommandContext *) g_queue_pop_head (self->priv->queue); - if (ctx) { - if (error) - g_simple_async_result_set_from_error (ctx->result, error); - else { - if (ctx->allow_cached && !error) - port_serial_set_cached_reply (self, ctx->command, self->priv->response); - - /* Upon completion, it is a task of the caller to remove from the response - * buffer the processed data */ - g_simple_async_result_set_op_res_gpointer (ctx->result, - g_byte_array_ref (self->priv->response), - (GDestroyNotify)g_byte_array_unref); + /* The completion of the command context may end up fully disposing the + * serial port object. In order to cope with that, we make sure we have + * our own reference to the object while the completion and follow up + * setup runs. */ + g_object_ref (self); + { + CommandContext *ctx; + + ctx = (CommandContext *) g_queue_pop_head (self->priv->queue); + if (ctx) { + /* Complete the command context with the appropriate result */ + if (error) + g_simple_async_result_set_from_error (ctx->result, error); + else { + if (ctx->allow_cached) + port_serial_set_cached_reply (self, ctx->command, parsed_response); + g_simple_async_result_set_op_res_gpointer (ctx->result, + g_byte_array_ref (parsed_response), + (GDestroyNotify) g_byte_array_unref); + } + + /* Don't complete in idle. We need the caller remove the response range which + * was processed, and that must be done before processing any new queued command */ + command_context_complete_and_free (ctx, FALSE); } - /* Don't complete in idle. We need the caller remove the response range which - * was processed, and that must be done before processing any new queued command */ - command_context_complete_and_free (ctx, FALSE); + if (!g_queue_is_empty (self->priv->queue)) + port_serial_schedule_queue_process (self, 0); } - - if (!g_queue_is_empty (self->priv->queue)) - port_serial_schedule_queue_process (self, 0); + g_object_unref (self); } static gboolean @@ -755,14 +778,21 @@ port_serial_timed_out (gpointer data) error = g_error_new_literal (MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT, "Serial command timed out"); - port_serial_got_response (self, error); - g_error_free (error); - /* Emit a timed out signal, used by upper layers to identify a disconnected - * serial port */ - g_signal_emit (self, signals[TIMED_OUT], 0, self->priv->n_consecutive_timeouts); + /* Make sure we have a valid reference when emitting the signal */ + g_object_ref (self); + { + port_serial_got_response (self, NULL, error); - return FALSE; + /* Emit a timed out signal, used by upper layers to identify a disconnected + * serial port */ + g_signal_emit (self, signals[TIMED_OUT], 0, self->priv->n_consecutive_timeouts); + } + g_object_unref (self); + + g_error_free (error); + + return G_SOURCE_REMOVE; } static void @@ -777,10 +807,10 @@ port_serial_response_wait_cancelled (GCancellable *cancellable, /* FIXME: This is not completely correct - if the response finally arrives and there's * some other command waiting for response right now, the other command will * get the output of the cancelled command. Not sure what to do here. */ - error = g_error_new_literal (MM_CORE_ERROR, - MM_CORE_ERROR_CANCELLED, + error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Waiting for the reply cancelled"); - port_serial_got_response (self, error); + /* Note: may complete last operation and unref the MMPortSerial */ + port_serial_got_response (self, NULL, error); g_error_free (error); } @@ -795,26 +825,21 @@ port_serial_queue_process (gpointer data) ctx = (CommandContext *) g_queue_peek_head (self->priv->queue); if (!ctx) - return FALSE; + return G_SOURCE_REMOVE; if (ctx->allow_cached) { const GByteArray *cached; cached = port_serial_get_cached_reply (self, ctx->command); if (cached) { - /* Ensure the response array is fully empty before setting the - * cached response. */ - if (self->priv->response->len > 0) { - mm_warn ("(%s) response array is not empty when using cached " - "reply, cleaning up %u bytes", - mm_port_get_device (MM_PORT (self)), - self->priv->response->len); - g_byte_array_set_size (self->priv->response, 0); - } - - g_byte_array_append (self->priv->response, cached->data, cached->len); - port_serial_got_response (self, NULL); - return FALSE; + GByteArray *parsed_response; + + parsed_response = g_byte_array_sized_new (cached->len); + g_byte_array_append (parsed_response, cached->data, cached->len); + /* Note: may complete last operation and unref the MMPortSerial */ + port_serial_got_response (self, parsed_response, NULL); + g_byte_array_unref (parsed_response); + return G_SOURCE_REMOVE; } /* Cached reply wasn't found, keep on */ @@ -822,9 +847,10 @@ port_serial_queue_process (gpointer data) /* If error, report it */ if (!port_serial_process_command (self, ctx, &error)) { - port_serial_got_response (self, error); + /* Note: may complete last operation and unref the MMPortSerial */ + port_serial_got_response (self, NULL, error); g_error_free (error); - return FALSE; + return G_SOURCE_REMOVE; } /* Schedule the next byte of the command to be sent */ @@ -833,44 +859,87 @@ port_serial_queue_process (gpointer data) (mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY ? self->priv->send_delay / 1000 : 0)); - return FALSE; + return G_SOURCE_REMOVE; } /* Setup the cancellable so that we can stop waiting for a response */ if (ctx->cancellable) { + gulong cancellable_id; + self->priv->cancellable = g_object_ref (ctx->cancellable); - self->priv->cancellable_id = (g_cancellable_connect ( - ctx->cancellable, - (GCallback)port_serial_response_wait_cancelled, - self, - NULL)); - if (!self->priv->cancellable_id) { - error = g_error_new (MM_CORE_ERROR, - MM_CORE_ERROR_CANCELLED, - "Won't wait for the reply"); - port_serial_got_response (self, error); - g_error_free (error); - return FALSE; - } + + /* If the GCancellable is already cancelled here, the callback will be + * called right away, and a GError will be propagated as response. In + * this case we need to completely avoid doing anything else with the + * MMPortSerial, as it may already be disposed. + * So, use an intermediate variable to store the cancellable id, and + * just return without further processing if we're already cancelled. + */ + cancellable_id = g_cancellable_connect (ctx->cancellable, + (GCallback)port_serial_response_wait_cancelled, + self, + NULL); + if (!cancellable_id) + return G_SOURCE_REMOVE; + + self->priv->cancellable_id = cancellable_id; } /* If the command is finished being sent, schedule the timeout */ self->priv->timeout_id = g_timeout_add_seconds (ctx->timeout, port_serial_timed_out, self); - return FALSE; + return G_SOURCE_REMOVE; } -static gboolean -parse_response (MMPortSerial *self, - GByteArray *response, - GError **error) +static void +parse_response_buffer (MMPortSerial *self) { - if (MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited) - MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited (self, response); + GError *error = NULL; + GByteArray *parsed_response = NULL; - g_return_val_if_fail (MM_PORT_SERIAL_GET_CLASS (self)->parse_response, FALSE); - return MM_PORT_SERIAL_GET_CLASS (self)->parse_response (self, response, error); + /* Parse unsolicited messages in the subclass. + * + * If any message found, it's processed immediately and the message is + * removed from the response buffer. + */ + if (MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited) + MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited (self, + self->priv->response); + + /* Parse response in the subclass. + * + * Returns TRUE either if an error is provided or if we really have the + * response to process. The parsed string is returned already out of the + * response buffer, and the response buffer is cleaned up accordingly. + */ + g_assert (MM_PORT_SERIAL_GET_CLASS (self)->parse_response != NULL); + switch (MM_PORT_SERIAL_GET_CLASS (self)->parse_response (self, + self->priv->response, + &parsed_response, + &error)) { + case MM_PORT_SERIAL_RESPONSE_BUFFER: + /* We have a valid response to process */ + g_assert (parsed_response); + self->priv->n_consecutive_timeouts = 0; + /* Note: may complete last operation and unref the MMPortSerial */ + port_serial_got_response (self, parsed_response, NULL); + g_byte_array_unref (parsed_response); + break; + case MM_PORT_SERIAL_RESPONSE_ERROR: + /* We have an error to process */ + g_assert (error); + self->priv->n_consecutive_timeouts = 0; + /* Note: may complete last operation and unref the MMPortSerial */ + port_serial_got_response (self, NULL, error); + g_error_free (error); + break; + case MM_PORT_SERIAL_RESPONSE_NONE: + /* Nothing to do this time */ + break; + default: + g_assert_not_reached (); + } } static gboolean @@ -881,31 +950,30 @@ common_input_available (MMPortSerial *self, gsize bytes_read; GIOStatus status = G_IO_STATUS_NORMAL; CommandContext *ctx; - const char *device; GError *error = NULL; + gboolean iterate = TRUE; + gboolean keep_source = G_SOURCE_CONTINUE; if (condition & G_IO_HUP) { - device = mm_port_get_device (MM_PORT (self)); - mm_dbg ("(%s) unexpected port hangup!", device); - + mm_obj_dbg (self, "unexpected port hangup!"); if (self->priv->response->len) g_byte_array_remove_range (self->priv->response, 0, self->priv->response->len); port_serial_close_force (self); - return FALSE; + return G_SOURCE_REMOVE; } if (condition & G_IO_ERR) { if (self->priv->response->len) g_byte_array_remove_range (self->priv->response, 0, self->priv->response->len); - return TRUE; + return G_SOURCE_CONTINUE; } /* Don't read any input if the current command isn't done being sent yet */ ctx = g_queue_peek_nth (self->priv->queue, 0); if (ctx && (ctx->started == TRUE) && (ctx->done == FALSE)) - return TRUE; + return G_SOURCE_CONTINUE; - do { + while (iterate) { bytes_read = 0; if (self->priv->iochannel) { @@ -915,35 +983,32 @@ common_input_available (MMPortSerial *self, &bytes_read, &error); if (status == G_IO_STATUS_ERROR) { - if (error) { - mm_warn ("(%s): read error: %s", - mm_port_get_device (MM_PORT (self)), - error->message); - } + if (error) + mm_obj_warn (self, "read error: %s", error->message); g_clear_error (&error); } } else if (self->priv->socket) { - bytes_read = g_socket_receive (self->priv->socket, - buf, - SERIAL_BUF_SIZE, - NULL, /* cancellable */ - &error); - if (bytes_read == -1) { + gssize sbytes_read; + + sbytes_read = g_socket_receive (self->priv->socket, + buf, + SERIAL_BUF_SIZE, + NULL, /* cancellable */ + &error); + if (sbytes_read < 0) { bytes_read = 0; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) status = G_IO_STATUS_AGAIN; else status = G_IO_STATUS_ERROR; - mm_warn ("(%s): receive error: %s", - mm_port_get_device (MM_PORT (self)), - error->message); + mm_obj_warn (self, "receive error: %s", error->message); g_clear_error (&error); - } else + } else { + bytes_read = (gsize) sbytes_read; status = G_IO_STATUS_NORMAL; + } } - - /* If no bytes read, just wait for more data */ if (bytes_read == 0) break; @@ -959,19 +1024,30 @@ common_input_available (MMPortSerial *self, g_byte_array_remove_range (self->priv->response, 0, (SERIAL_BUF_SIZE / 2)); } - /* Parse response. Returns TRUE either if an error is provided or if - * we really have the response to process. */ - if (parse_response (self, self->priv->response, &error)) { - /* Reset number of consecutive timeouts only here */ - self->priv->n_consecutive_timeouts = 0; - /* Process response retrieved */ - port_serial_got_response (self, error); - g_clear_error (&error); + /* See if we can parse anything. The response parsing may actually + * schedule the completion of a serial command, and that in turn may end + * up fully disposing this serial port object. In order to cope with + * that we make sure we have our own reference to the object while the + * response buffer operation is run, and then we check ourselves whether + * we should be keeping this socket/iochannel source or not. */ + g_object_ref (self); + { + parse_response_buffer (self); + + /* If we didn't end up closing the iochannel/socket in the previous + * operation, we keep this source. */ + keep_source = ((self->priv->iochannel_id > 0 || self->priv->socket_source != NULL) ? + G_SOURCE_CONTINUE : G_SOURCE_REMOVE); + + /* If we're keeping the source and we still may have bytes to read, + * iterate. */ + iterate = ((keep_source == G_SOURCE_CONTINUE) && + (bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN)); } - } while ( (bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN) - && (self->priv->iochannel_id > 0 || self->priv->socket_source != NULL)); + g_object_unref (self); + } - return TRUE; + return keep_source; } static gboolean @@ -996,7 +1072,6 @@ data_watch_enable (MMPortSerial *self, gboolean enable) if (self->priv->iochannel_id) { if (enable) g_warn_if_fail (self->priv->iochannel_id == 0); - g_source_remove (self->priv->iochannel_id); self->priv->iochannel_id = 0; } @@ -1045,11 +1120,9 @@ port_connected (MMPortSerial *self, GParamSpec *pspec, gpointer user_data) connected = mm_port_get_connected (MM_PORT (self)); if (self->priv->fd >= 0 && ioctl (self->priv->fd, (connected ? TIOCNXCL : TIOCEXCL)) < 0) { - mm_warn ("(%s): could not %s serial port lock: (%d) %s", - mm_port_get_device (MM_PORT (self)), - connected ? "drop" : "re-acquire", - errno, - strerror (errno)); + mm_obj_warn (self, "could not %s serial port lock: %s", + connected ? "drop" : "re-acquire", + g_strerror (errno)); if (!connected) { // FIXME: do something here, maybe try again in a few seconds or // close the port and error out? @@ -1082,7 +1155,7 @@ mm_port_serial_open (MMPortSerial *self, GError **error) return FALSE; } - if (self->priv->reopen_ctx) { + if (self->priv->reopen_task) { g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED, @@ -1091,12 +1164,21 @@ mm_port_serial_open (MMPortSerial *self, GError **error) return FALSE; } + if (mm_port_get_connected (MM_PORT (self))) { + g_set_error (error, + MM_SERIAL_ERROR, + MM_SERIAL_ERROR_OPEN_FAILED, + "Could not open serial device %s: port is connected", + device); + return FALSE; + } + if (self->priv->open_count) { /* Already open */ goto success; } - mm_dbg ("(%s) opening serial port...", device); + mm_obj_dbg (self, "opening serial port..."); g_get_current_time (&tv_start); @@ -1120,7 +1202,6 @@ mm_port_serial_open (MMPortSerial *self, GError **error) MM_SERIAL_ERROR, (errno == ENODEV) ? MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE : MM_SERIAL_ERROR_OPEN_FAILED, "Could not open serial device %s: %s", device, strerror (errno_save)); - mm_warn ("(%s) could not open serial device (%d)", device, errno_save); return FALSE; } } @@ -1132,21 +1213,12 @@ mm_port_serial_open (MMPortSerial *self, GError **error) errno_save = errno; g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED, "Could not lock serial device %s: %s", device, strerror (errno_save)); - mm_warn ("(%s) could not lock serial device (%d)", device, errno_save); goto error; } /* Flush any waiting IO */ tcflush (self->priv->fd, TCIOFLUSH); - if (tcgetattr (self->priv->fd, &self->priv->old_t) < 0) { - errno_save = errno; - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED, - "Could not set attributes on serial device %s: %s", device, strerror (errno_save)); - mm_warn ("(%s) could not set attributes on serial device (%d)", device, errno_save); - goto error; - } - /* Don't wait for pending data when closing the port; this can cause some * stupid devices that don't respond to URBs on a particular port to hang * for 30 seconds when probing fails. See GNOME bug #630670. @@ -1154,21 +1226,21 @@ mm_port_serial_open (MMPortSerial *self, GError **error) if (ioctl (self->priv->fd, TIOCGSERIAL, &sinfo) == 0) { sinfo.closing_wait = ASYNC_CLOSING_WAIT_NONE; if (ioctl (self->priv->fd, TIOCSSERIAL, &sinfo) < 0) - mm_warn ("(%s): couldn't set serial port closing_wait to none: %s", - device, g_strerror (errno)); + mm_obj_warn (self, "couldn't set serial port closing_wait to none: %s", g_strerror (errno)); } } g_warn_if_fail (MM_PORT_SERIAL_GET_CLASS (self)->config_fd); - if (self->priv->fd >= 0 && !MM_PORT_SERIAL_GET_CLASS (self)->config_fd (self, self->priv->fd, error)) { - mm_dbg ("(%s) failed to configure serial device", device); + if (self->priv->fd >= 0 && mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_WWAN && + !MM_PORT_SERIAL_GET_CLASS (self)->config_fd (self, self->priv->fd, error)) { + mm_obj_dbg (self, "failed to configure serial device"); goto error; } g_get_current_time (&tv_end); if (tv_end.tv_sec - tv_start.tv_sec > 7) - mm_warn ("(%s): open blocked by driver for more than 7 seconds!", device); + mm_obj_warn (self, "open blocked by driver for more than 7 seconds!"); if (mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_UNIX) { /* Create new GIOChannel */ @@ -1234,7 +1306,7 @@ mm_port_serial_open (MMPortSerial *self, GError **error) success: self->priv->open_count++; - mm_dbg ("(%s) device open count is %d (open)", device, self->priv->open_count); + mm_obj_dbg (self, "device open count is %d (open)", self->priv->open_count); /* Run additional port config if just opened */ if (self->priv->open_count == 1 && MM_PORT_SERIAL_GET_CLASS (self)->config) @@ -1243,10 +1315,9 @@ success: return TRUE; error: - mm_warn ("(%s) failed to open serial device", device); + mm_obj_warn (self, "failed to open serial device"); if (self->priv->iochannel) { - g_io_channel_shutdown (self->priv->iochannel, FALSE, NULL); g_io_channel_unref (self->priv->iochannel); self->priv->iochannel = NULL; } @@ -1277,8 +1348,7 @@ mm_port_serial_is_open (MMPortSerial *self) static void _close_internal (MMPortSerial *self, gboolean force) { - const char *device; - int i; + guint i; g_return_if_fail (MM_IS_PORT_SERIAL (self)); @@ -1289,15 +1359,16 @@ _close_internal (MMPortSerial *self, gboolean force) self->priv->open_count--; } - device = mm_port_get_device (MM_PORT (self)); - - mm_dbg ("(%s) device open count is %d (close)", device, self->priv->open_count); + mm_obj_dbg (self, "device open count is %d (close)", self->priv->open_count); if (self->priv->open_count > 0) return; if (self->priv->connected_id) { - g_signal_handler_disconnect (self, self->priv->connected_id); + /* Don't assume it's always connected, because it may be automatically connected during + * object disposal, and this method is also called in finalize() */ + if (g_signal_handler_is_connected (self, self->priv->connected_id)) + g_signal_handler_disconnect (self, self->priv->connected_id); self->priv->connected_id = 0; } @@ -1307,7 +1378,7 @@ _close_internal (MMPortSerial *self, gboolean force) GTimeVal tv_start, tv_end; struct serial_struct sinfo = { 0 }; - mm_dbg ("(%s) closing serial port...", device); + mm_obj_dbg (self, "closing serial port..."); mm_port_set_connected (MM_PORT (self), FALSE); @@ -1320,22 +1391,22 @@ _close_internal (MMPortSerial *self, gboolean force) */ if (ioctl (self->priv->fd, TIOCGSERIAL, &sinfo) == 0) { if (sinfo.closing_wait != ASYNC_CLOSING_WAIT_NONE) { - mm_warn ("(%s): serial port closing_wait was reset!", device); + mm_obj_warn (self, "serial port closing_wait was reset!"); sinfo.closing_wait = ASYNC_CLOSING_WAIT_NONE; if (ioctl (self->priv->fd, TIOCSSERIAL, &sinfo) < 0) - mm_warn ("(%s): couldn't set serial port closing_wait to none: %s", - device, g_strerror (errno)); + mm_obj_warn (self, "couldn't set serial port closing_wait to none: %s", g_strerror (errno)); } } - tcsetattr (self->priv->fd, TCSANOW, &self->priv->old_t); tcflush (self->priv->fd, TCIOFLUSH); } /* Destroy channel */ if (self->priv->iochannel) { data_watch_enable (self, FALSE); - g_io_channel_shutdown (self->priv->iochannel, TRUE, NULL); + /* unref() without g_io_channel_shutdown() to destroy the channel + * without closing the fd. The close() is called explicitly after. + */ g_io_channel_unref (self->priv->iochannel); self->priv->iochannel = NULL; } @@ -1356,7 +1427,7 @@ _close_internal (MMPortSerial *self, gboolean force) g_get_current_time (&tv_end); - mm_dbg ("(%s) serial port closed", device); + mm_obj_dbg (self, "serial port closed"); /* Some ports don't respond to data and when close is called * the serial layer waits up to 30 second (closing_wait) for @@ -1364,7 +1435,7 @@ _close_internal (MMPortSerial *self, gboolean force) * Log that. See GNOME bug #630670 for more details. */ if (tv_end.tv_sec - tv_start.tv_sec > 7) - mm_warn ("(%s): close blocked by driver for more than 7 seconds!", device); + mm_obj_warn (self, "close blocked by driver for more than 7 seconds!"); } /* Clear the command queue */ @@ -1418,7 +1489,7 @@ port_serial_close_force (MMPortSerial *self) if (self->priv->forced_close) return; - mm_dbg ("(%s) forced to close port", mm_port_get_device (MM_PORT (self))); + mm_obj_dbg (self, "forced to close port"); /* Mark as having forced the close, so that we don't warn about incorrect * open counts */ @@ -1440,20 +1511,15 @@ port_serial_close_force (MMPortSerial *self) /* Reopen */ typedef struct { - MMPortSerial *self; - GSimpleAsyncResult *result; guint initial_open_count; guint reopen_id; } ReopenContext; static void -reopen_context_complete_and_free (ReopenContext *ctx) +reopen_context_free (ReopenContext *ctx) { if (ctx->reopen_id) g_source_remove (ctx->reopen_id); - g_simple_async_result_complete_in_idle (ctx->result); - g_object_unref (ctx->result); - g_object_unref (ctx->self); g_slice_free (ReopenContext, ctx); } @@ -1462,56 +1528,64 @@ mm_port_serial_reopen_finish (MMPortSerial *port, GAsyncResult *res, GError **error) { - return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); + return g_task_propagate_boolean (G_TASK (res), error); } static void port_serial_reopen_cancel (MMPortSerial *self) { - ReopenContext *ctx; + GTask *task; - if (!self->priv->reopen_ctx) + if (!self->priv->reopen_task) return; - /* Recover context */ - ctx = (ReopenContext *)self->priv->reopen_ctx; - self->priv->reopen_ctx = NULL; + /* Recover task */ + task = self->priv->reopen_task; + self->priv->reopen_task = NULL; - g_simple_async_result_set_error (ctx->result, - MM_CORE_ERROR, - MM_CORE_ERROR_CANCELLED, - "Reopen cancelled"); - reopen_context_complete_and_free (ctx); + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Reopen cancelled"); + g_object_unref (task); } static gboolean reopen_do (MMPortSerial *self) { + GTask *task; ReopenContext *ctx; GError *error = NULL; guint i; - /* Recover context */ - g_assert (self->priv->reopen_ctx != NULL); - ctx = (ReopenContext *)self->priv->reopen_ctx; - self->priv->reopen_ctx = NULL; + /* Recover task */ + g_assert (self->priv->reopen_task != NULL); + task = self->priv->reopen_task; + self->priv->reopen_task = NULL; + ctx = g_task_get_task_data (task); ctx->reopen_id = 0; for (i = 0; i < ctx->initial_open_count; i++) { - if (!mm_port_serial_open (ctx->self, &error)) { + if (!mm_port_serial_open (self, &error)) { g_prefix_error (&error, "Couldn't reopen port (%u): ", i); break; } } - if (error) - g_simple_async_result_take_error (ctx->result, error); - else - g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); - reopen_context_complete_and_free (ctx); + if (error) { + /* An error during port reopening may mean that the device is + * already gone. Note that we won't get a HUP in the TTY when + * the port is gone during the reopen wait time, because there's + * no channel I/O monitoring in place. + * + * If we ever see this, we'll flag the port as forced close right + * away, because the open count would anyway be broken afterwards. + */ + port_serial_close_force (self); + g_task_return_error (task, error); + } else + g_task_return_boolean (task, TRUE); + g_object_unref (task); - return FALSE; + return G_SOURCE_REMOVE; } void @@ -1521,41 +1595,38 @@ mm_port_serial_reopen (MMPortSerial *self, gpointer user_data) { ReopenContext *ctx; + GTask *task; guint i; g_return_if_fail (MM_IS_PORT_SERIAL (self)); /* Setup context */ ctx = g_slice_new0 (ReopenContext); - ctx->self = g_object_ref (self); - ctx->result = g_simple_async_result_new (G_OBJECT (self), - callback, - user_data, - mm_port_serial_reopen); ctx->initial_open_count = self->priv->open_count; + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)reopen_context_free); + if (self->priv->forced_close) { - g_simple_async_result_set_error (ctx->result, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "Serial port has been forced close."); - reopen_context_complete_and_free (ctx); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Serial port has been forced close."); + g_object_unref (task); return; } /* If already reopening, halt */ - if (self->priv->reopen_ctx) { - g_simple_async_result_set_error (ctx->result, - MM_CORE_ERROR, - MM_CORE_ERROR_IN_PROGRESS, - "Modem is already being reopened"); - reopen_context_complete_and_free (ctx); + if (self->priv->reopen_task) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_IN_PROGRESS, + "Modem is already being reopened"); + g_object_unref (task); return; } - mm_dbg ("(%s) reopening port (%u)", - mm_port_get_device (MM_PORT (self)), - ctx->initial_open_count); + mm_obj_dbg (self, "reopening port (%u)", ctx->initial_open_count); for (i = 0; i < ctx->initial_open_count; i++) mm_port_serial_close (self); @@ -1566,7 +1637,7 @@ mm_port_serial_reopen (MMPortSerial *self, ctx->reopen_id = g_idle_add ((GSourceFunc)reopen_do, self); /* Store context in private info */ - self->priv->reopen_ctx = ctx; + self->priv->reopen_task = task; } static gboolean @@ -1594,15 +1665,11 @@ static gboolean set_speed (MMPortSerial *self, speed_t speed, GError **error) { struct termios options; - int fd, count = 4; - gboolean success = FALSE; g_assert (self->priv->fd >= 0); - fd = self->priv->fd; - memset (&options, 0, sizeof (struct termios)); - if (tcgetattr (fd, &options) != 0) { + if (tcgetattr (self->priv->fd, &options) != 0) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, @@ -1615,60 +1682,22 @@ set_speed (MMPortSerial *self, speed_t speed, GError **error) cfsetospeed (&options, speed); options.c_cflag |= (CLOCAL | CREAD); - /* Configure flow control as well here */ - if (self->priv->rts_cts) - options.c_cflag |= (CRTSCTS); - - while (count-- > 0) { - if (tcsetattr (fd, TCSANOW, &options) == 0) { - success = TRUE; - break; /* Operation successful */ - } - - /* Try a few times if EAGAIN */ - if (errno == EAGAIN) - g_usleep (100000); - else { - /* If not EAGAIN, hard error */ - g_set_error (error, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "%s: tcsetattr() error %d", - __func__, errno); - return FALSE; - } - } - - if (!success) { - g_set_error (error, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "%s: tcsetattr() retry timeout", - __func__); - return FALSE; - } - - return TRUE; + return internal_tcsetattr (self, self->priv->fd, &options, error); } /*****************************************************************************/ /* Flash */ typedef struct { - GSimpleAsyncResult *result; - MMPortSerial *self; speed_t current_speed; guint flash_id; } FlashContext; static void -flash_context_complete_and_free (FlashContext *ctx) +flash_context_free (FlashContext *ctx) { if (ctx->flash_id) g_source_remove (ctx->flash_id); - g_simple_async_result_complete_in_idle (ctx->result); - g_object_unref (ctx->result); - g_object_unref (ctx->self); g_slice_free (FlashContext, ctx); } @@ -1677,44 +1706,63 @@ mm_port_serial_flash_finish (MMPortSerial *port, GAsyncResult *res, GError **error) { - return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); + return g_task_propagate_boolean (G_TASK (res), error); +} + +static gboolean +flash_cancel_cb (GTask *task) +{ + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Flash cancelled"); + g_object_unref (task); + return G_SOURCE_REMOVE; } void mm_port_serial_flash_cancel (MMPortSerial *self) { FlashContext *ctx; + GTask *task; - if (!self->priv->flash_ctx) + /* Do nothing if there is no flash task */ + if (!self->priv->flash_task) return; - /* Recover context */ - ctx = (FlashContext *)self->priv->flash_ctx; - self->priv->flash_ctx = NULL; + /* Recover task */ + task = self->priv->flash_task; + self->priv->flash_task = NULL; + + /* If flash operation is scheduled, unschedule it */ + ctx = g_task_get_task_data (task); + if (ctx->flash_id) { + g_source_remove (ctx->flash_id); + ctx->flash_id = 0; + } - g_simple_async_result_set_error (ctx->result, - MM_CORE_ERROR, - MM_CORE_ERROR_CANCELLED, - "Flash cancelled"); - flash_context_complete_and_free (ctx); + /* Schedule task to be cancelled in an idle. + * We do NOT want this cancellation to happen right away, + * because the object reference in the flashing task may + * be the last one valid. */ + g_idle_add ((GSourceFunc)flash_cancel_cb, task); } static gboolean flash_do (MMPortSerial *self) { + GTask *task; FlashContext *ctx; GError *error = NULL; - /* Recover context */ - g_assert (self->priv->flash_ctx != NULL); - ctx = (FlashContext *)self->priv->flash_ctx; - self->priv->flash_ctx = NULL; + /* Recover task */ + g_assert (self->priv->flash_task != NULL); + task = self->priv->flash_task; + self->priv->flash_task = NULL; + ctx = g_task_get_task_data (task); ctx->flash_id = 0; if (self->priv->flash_ok && mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY) { if (ctx->current_speed) { - if (!set_speed (ctx->self, ctx->current_speed, &error)) + if (!set_speed (self, ctx->current_speed, &error)) g_assert (error); } else { error = g_error_new_literal (MM_SERIAL_ERROR, @@ -1724,12 +1772,12 @@ flash_do (MMPortSerial *self) } if (error) - g_simple_async_result_take_error (ctx->result, error); + g_task_return_error (task, error); else - g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); - flash_context_complete_and_free (ctx); + g_task_return_boolean (task, TRUE); + g_object_unref (task); - return FALSE; + return G_SOURCE_REMOVE; } void @@ -1740,6 +1788,7 @@ mm_port_serial_flash (MMPortSerial *self, gpointer user_data) { FlashContext *ctx; + GTask *task; GError *error = NULL; gboolean success; @@ -1747,33 +1796,31 @@ mm_port_serial_flash (MMPortSerial *self, /* Setup context */ ctx = g_slice_new0 (FlashContext); - ctx->self = g_object_ref (self); - ctx->result = g_simple_async_result_new (G_OBJECT (self), - callback, - user_data, - mm_port_serial_flash); + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)flash_context_free); if (!mm_port_serial_is_open (self)) { - g_simple_async_result_set_error (ctx->result, - MM_SERIAL_ERROR, - MM_SERIAL_ERROR_NOT_OPEN, - "The serial port is not open."); - flash_context_complete_and_free (ctx); + g_task_return_new_error (task, + MM_SERIAL_ERROR, + MM_SERIAL_ERROR_NOT_OPEN, + "The serial port is not open."); + g_object_unref (task); return; } - if (self->priv->flash_ctx) { - g_simple_async_result_set_error (ctx->result, - MM_CORE_ERROR, - MM_CORE_ERROR_IN_PROGRESS, - "Modem is already being flashed."); - flash_context_complete_and_free (ctx); + if (self->priv->flash_task) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_IN_PROGRESS, + "Modem is already being flashed."); + g_object_unref (task); return; } /* Flashing only in TTY */ if (!self->priv->flash_ok || mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_TTY) { - self->priv->flash_ctx = ctx; + self->priv->flash_task = task; ctx->flash_id = g_idle_add ((GSourceFunc)flash_do, self); return; } @@ -1781,26 +1828,74 @@ mm_port_serial_flash (MMPortSerial *self, /* Grab current speed so we can reset it after flashing */ success = get_speed (self, &ctx->current_speed, &error); if (!success && !ignore_errors) { - g_simple_async_result_take_error (ctx->result, error); - flash_context_complete_and_free (ctx); + g_task_return_error (task, error); + g_object_unref (task); return; } g_clear_error (&error); success = set_speed (self, B0, &error); if (!success && !ignore_errors) { - g_simple_async_result_take_error (ctx->result, error); - flash_context_complete_and_free (ctx); + g_task_return_error (task, error); + g_object_unref (task); return; } g_clear_error (&error); - self->priv->flash_ctx = ctx; + self->priv->flash_task = task; ctx->flash_id = g_timeout_add (flash_time, (GSourceFunc)flash_do, self); } /*****************************************************************************/ +gboolean +mm_port_serial_set_flow_control (MMPortSerial *self, + MMFlowControl flow_control, + GError **error) +{ + struct termios options; + gchar *flow_control_str = NULL; + GError *inner_error = NULL; + + /* retrieve current settings */ + memset (&options, 0, sizeof (struct termios)); + if (tcgetattr (self->priv->fd, &options) != 0) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't get serial port attributes: %s", g_strerror (errno)); + goto out; + } + + flow_control_str = mm_flow_control_build_string_from_mask (flow_control); + + /* Return if current settings are already what we want */ + if (!set_flow_control_termios (self, flow_control, &options)) { + mm_obj_dbg (self, "no need to change flow control settings: already %s", flow_control_str); + goto out; + } + + if (!internal_tcsetattr (self, self->priv->fd, &options, &inner_error)) + goto out; + + mm_obj_dbg (self, "flow control settings updated to %s", flow_control_str); + + out: + g_free (flow_control_str); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + return TRUE; +} + +MMFlowControl +mm_port_serial_get_flow_control (MMPortSerial *self) +{ + return self->priv->flow_control; +} + +/*****************************************************************************/ + MMPortSerial * mm_port_serial_new (const char *name, MMPortType ptype) { @@ -1865,6 +1960,7 @@ mm_port_serial_init (MMPortSerial *self) self->priv->bits = 8; self->priv->parity = 'n'; self->priv->stopbits = 1; + self->priv->flow_control = MM_FLOW_CONTROL_UNKNOWN; self->priv->send_delay = 1000; self->priv->queue = g_queue_new (); @@ -1895,15 +1991,15 @@ set_property (GObject *object, case PROP_STOPBITS: self->priv->stopbits = g_value_get_uint (value); break; + case PROP_FLOW_CONTROL: + self->priv->flow_control = g_value_get_flags (value); + break; case PROP_SEND_DELAY: self->priv->send_delay = g_value_get_uint64 (value); break; case PROP_SPEW_CONTROL: self->priv->spew_control = g_value_get_boolean (value); break; - case PROP_RTS_CTS: - self->priv->rts_cts = g_value_get_boolean (value); - break; case PROP_FLASH_OK: self->priv->flash_ok = g_value_get_boolean (value); break; @@ -1937,15 +2033,15 @@ get_property (GObject *object, case PROP_STOPBITS: g_value_set_uint (value, self->priv->stopbits); break; + case PROP_FLOW_CONTROL: + g_value_set_flags (value, self->priv->flow_control); + break; case PROP_SEND_DELAY: g_value_set_uint64 (value, self->priv->send_delay); break; case PROP_SPEW_CONTROL: g_value_set_boolean (value, self->priv->spew_control); break; - case PROP_RTS_CTS: - g_value_set_boolean (value, self->priv->rts_cts); - break; case PROP_FLASH_OK: g_value_set_boolean (value, self->priv->flash_ok); break; @@ -1956,25 +2052,24 @@ get_property (GObject *object, } static void -dispose (GObject *object) +finalize (GObject *object) { MMPortSerial *self = MM_PORT_SERIAL (object); - if (self->priv->timeout_id) { - g_source_remove (self->priv->timeout_id); - self->priv->timeout_id = 0; - } - - port_serial_close_force (MM_PORT_SERIAL (object)); + port_serial_close_force (MM_PORT_SERIAL (object)); mm_port_serial_flash_cancel (MM_PORT_SERIAL (object)); - G_OBJECT_CLASS (mm_port_serial_parent_class)->dispose (object); -} + /* These are disposed during port closing */ + g_assert (self->priv->iochannel == NULL); + g_assert (self->priv->iochannel_id == 0); + g_assert (self->priv->socket == NULL); + g_assert (self->priv->socket_source == NULL); -static void -finalize (GObject *object) -{ - MMPortSerial *self = MM_PORT_SERIAL (object); + if (self->priv->timeout_id) + g_source_remove (self->priv->timeout_id); + + if (self->priv->queue_id) + g_source_remove (self->priv->queue_id); g_hash_table_destroy (self->priv->reply_cache); g_byte_array_unref (self->priv->response); @@ -1993,8 +2088,7 @@ mm_port_serial_class_init (MMPortSerialClass *klass) /* Virtual methods */ object_class->set_property = set_property; object_class->get_property = get_property; - object_class->dispose = dispose; - object_class->finalize = finalize; + object_class->finalize = finalize; klass->config_fd = real_config_fd; @@ -2040,6 +2134,15 @@ mm_port_serial_class_init (MMPortSerialClass *klass) G_PARAM_READWRITE)); g_object_class_install_property + (object_class, PROP_FLOW_CONTROL, + g_param_spec_flags (MM_PORT_SERIAL_FLOW_CONTROL, + "FlowControl", + "Select flow control", + MM_TYPE_FLOW_CONTROL, + MM_FLOW_CONTROL_UNKNOWN, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SEND_DELAY, g_param_spec_uint64 (MM_PORT_SERIAL_SEND_DELAY, "SendDelay", @@ -2056,21 +2159,13 @@ mm_port_serial_class_init (MMPortSerialClass *klass) G_PARAM_READWRITE)); g_object_class_install_property - (object_class, PROP_RTS_CTS, - g_param_spec_boolean (MM_PORT_SERIAL_RTS_CTS, - "RTSCTS", - "Enable RTS/CTS flow control", - FALSE, - G_PARAM_READWRITE)); - - g_object_class_install_property (object_class, PROP_FLASH_OK, g_param_spec_boolean (MM_PORT_SERIAL_FLASH_OK, "FlashOk", "Flashing the port (0 baud for a short period) " "is allowed.", TRUE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + G_PARAM_READWRITE)); /* Signals */ signals[BUFFER_FULL] = |