aboutsummaryrefslogtreecommitdiff
path: root/src/mm-port-serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-port-serial.c')
-rw-r--r--src/mm-port-serial.c1847
1 files changed, 1847 insertions, 0 deletions
diff --git a/src/mm-port-serial.c b/src/mm-port-serial.c
new file mode 100644
index 00000000..44d47990
--- /dev/null
+++ b/src/mm-port-serial.c
@@ -0,0 +1,1847 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#define _GNU_SOURCE /* for strcasestr() */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <linux/serial.h>
+
+#include <ModemManager.h>
+#include <mm-errors-types.h>
+
+#include "mm-port-serial.h"
+#include "mm-log.h"
+
+static gboolean mm_port_serial_queue_process (gpointer data);
+static void mm_port_serial_close_force (MMPortSerial *self);
+
+G_DEFINE_TYPE (MMPortSerial, mm_port_serial, MM_TYPE_PORT)
+
+enum {
+ PROP_0,
+ PROP_BAUD,
+ PROP_BITS,
+ PROP_PARITY,
+ PROP_STOPBITS,
+ PROP_SEND_DELAY,
+ PROP_FD,
+ PROP_SPEW_CONTROL,
+ PROP_RTS_CTS,
+ PROP_FLASH_OK,
+
+ LAST_PROP
+};
+
+enum {
+ BUFFER_FULL,
+ TIMED_OUT,
+ FORCED_CLOSE,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define SERIAL_BUF_SIZE 2048
+
+#define MM_PORT_SERIAL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PORT_SERIAL, MMPortSerialPrivate))
+
+typedef struct {
+ guint32 open_count;
+ gboolean forced_close;
+ int fd;
+ GHashTable *reply_cache;
+ GIOChannel *channel;
+ GQueue *queue;
+ GByteArray *response;
+
+ struct termios old_t;
+
+ guint baud;
+ guint bits;
+ char parity;
+ guint stopbits;
+ guint64 send_delay;
+ gboolean spew_control;
+ gboolean rts_cts;
+ gboolean flash_ok;
+
+ guint queue_id;
+ guint watch_id;
+ guint timeout_id;
+
+ GCancellable *cancellable;
+ gulong cancellable_id;
+
+ guint n_consecutive_timeouts;
+
+ guint flash_id;
+ guint reopen_id;
+ guint connected_id;
+} MMPortSerialPrivate;
+
+typedef struct {
+ GByteArray *command;
+ guint32 idx;
+ guint32 eagain_count;
+ gboolean started;
+ gboolean done;
+ GCallback callback;
+ gpointer user_data;
+ guint32 timeout;
+ gboolean cached;
+ GCancellable *cancellable;
+} MMQueueData;
+
+#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)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (port);
+ struct termios stbuf;
+ int err;
+
+ err = tcgetattr (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)
+{
+ int speed;
+
+ switch (i) {
+ case 0:
+ speed = B0;
+ break;
+ case 50:
+ speed = B50;
+ break;
+ case 75:
+ speed = B75;
+ break;
+ case 110:
+ speed = B110;
+ break;
+ case 150:
+ speed = B150;
+ break;
+ case 300:
+ speed = B300;
+ break;
+ case 600:
+ speed = B600;
+ break;
+ case 1200:
+ speed = B1200;
+ break;
+ case 2400:
+ speed = B2400;
+ break;
+ case 4800:
+ speed = B4800;
+ break;
+ case 9600:
+ speed = B9600;
+ break;
+ case 19200:
+ speed = B19200;
+ break;
+ case 38400:
+ speed = B38400;
+ break;
+ case 57600:
+ speed = B57600;
+ break;
+ case 115200:
+ speed = B115200;
+ break;
+ case 460800:
+ speed = B460800;
+ break;
+ default:
+ mm_warn ("Invalid baudrate '%d'", i);
+ speed = B9600;
+ }
+
+ return speed;
+}
+
+static int
+parse_bits (guint i)
+{
+ int bits;
+
+ switch (i) {
+ case 5:
+ bits = CS5;
+ break;
+ case 6:
+ bits = CS6;
+ break;
+ case 7:
+ bits = CS7;
+ break;
+ case 8:
+ bits = CS8;
+ break;
+ default:
+ mm_warn ("Invalid bits (%d). Valid values are 5, 6, 7, 8.", i);
+ bits = CS8;
+ }
+
+ return bits;
+}
+
+static int
+parse_parity (char c)
+{
+ int parity;
+
+ switch (c) {
+ case 'n':
+ case 'N':
+ parity = 0;
+ break;
+ case 'e':
+ case 'E':
+ parity = PARENB;
+ break;
+ case 'o':
+ case 'O':
+ parity = PARENB | PARODD;
+ break;
+ default:
+ mm_warn ("Invalid parity (%c). Valid values are n, e, o", c);
+ parity = 0;
+ }
+
+ return parity;
+}
+
+static int
+parse_stopbits (guint i)
+{
+ int stopbits;
+
+ switch (i) {
+ case 1:
+ stopbits = 0;
+ break;
+ case 2:
+ stopbits = CSTOPB;
+ break;
+ default:
+ mm_warn ("Invalid stop bits (%d). Valid values are 1 and 2)", i);
+ stopbits = 0;
+ }
+
+ return stopbits;
+}
+
+static gboolean
+real_config_fd (MMPortSerial *self, int fd, GError **error)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ struct termios stbuf, other;
+ int speed;
+ int bits;
+ int parity;
+ int stopbits;
+
+ speed = parse_baudrate (priv->baud);
+ bits = parse_bits (priv->bits);
+ parity = parse_parity (priv->parity);
+ stopbits = parse_stopbits (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);
+ }
+
+ stbuf.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY );
+ stbuf.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET);
+ stbuf.c_lflag &= ~(ICANON | XCASE | ECHO | ECHOE | ECHONL);
+ stbuf.c_lflag &= ~(ECHO | ECHOE);
+ 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);
+
+ /* 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);
+
+ errno = 0;
+ if (cfsetispeed (&stbuf, speed) != 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "%s: failed to set serial port input speed; errno %d",
+ __func__, errno);
+ return FALSE;
+ }
+
+ errno = 0;
+ if (cfsetospeed (&stbuf, speed) != 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "%s: failed to set serial port output speed; errno %d",
+ __func__, errno);
+ 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;
+ }
+
+ /* 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);
+ }
+
+ if (memcmp (&stbuf, &other, sizeof (other)) != 0) {
+ mm_warn ("(%s): port attributes not fully set",
+ mm_port_get_device (MM_PORT (self)));
+ }
+
+ return TRUE;
+}
+
+static void
+serial_debug (MMPortSerial *self, const char *prefix, const char *buf, gsize len)
+{
+ g_return_if_fail (len > 0);
+
+ if (MM_PORT_SERIAL_GET_CLASS (self)->debug_log)
+ MM_PORT_SERIAL_GET_CLASS (self)->debug_log (self, prefix, buf, len);
+}
+
+static gboolean
+mm_port_serial_process_command (MMPortSerial *self,
+ MMQueueData *info,
+ GError **error)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ const guint8 *p;
+ int status, expected_status, send_len;
+
+ if (priv->fd < 0) {
+ g_set_error_literal (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
+ "Sending command failed: device is not enabled");
+ return FALSE;
+ }
+
+ if (mm_port_get_connected (MM_PORT (self))) {
+ g_set_error_literal (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
+ "Sending command failed: device is connected");
+ return FALSE;
+ }
+
+ /* Only print command the first time */
+ if (info->started == FALSE) {
+ info->started = TRUE;
+ serial_debug (self, "-->", (const char *) info->command->data, info->command->len);
+ }
+
+ if (priv->send_delay == 0) {
+ /* Send the whole command in one write */
+ send_len = expected_status = info->command->len;
+ p = info->command->data;
+ } else {
+ /* Send just one byte of the command */
+ send_len = expected_status = 1;
+ p = &info->command->data[info->idx];
+ }
+
+ /* Send a single byte of the command */
+ errno = 0;
+ status = write (priv->fd, p, send_len);
+ if (status > 0)
+ info->idx += status;
+ else {
+ /* Error or no bytes written */
+ if (errno == EAGAIN || status == 0) {
+ info->eagain_count--;
+ if (info->eagain_count <= 0) {
+ /* If we reach the limit of EAGAIN errors, treat as a timeout error. */
+ priv->n_consecutive_timeouts++;
+ g_signal_emit (self, signals[TIMED_OUT], 0, priv->n_consecutive_timeouts);
+
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
+ "Sending command failed: '%s'", strerror (errno));
+ return FALSE;
+ }
+ } else {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
+ "Sending command failed: '%s'", strerror (errno));
+ return FALSE;
+ }
+ }
+
+ if (info->idx >= info->command->len)
+ info->done = TRUE;
+
+ return TRUE;
+}
+
+static void
+mm_port_serial_set_cached_reply (MMPortSerial *self,
+ const GByteArray *command,
+ const GByteArray *response)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_PORT_SERIAL (self));
+ g_return_if_fail (command != NULL);
+
+ if (response) {
+ GByteArray *cmd_copy = g_byte_array_sized_new (command->len);
+ GByteArray *rsp_copy = g_byte_array_sized_new (response->len);
+
+ g_byte_array_append (cmd_copy, command->data, command->len);
+ g_byte_array_append (rsp_copy, response->data, response->len);
+ g_hash_table_insert (priv->reply_cache, cmd_copy, rsp_copy);
+ } else
+ g_hash_table_remove (MM_PORT_SERIAL_GET_PRIVATE (self)->reply_cache, command);
+}
+
+static const GByteArray *
+mm_port_serial_get_cached_reply (MMPortSerial *self, GByteArray *command)
+{
+ return (const GByteArray *) g_hash_table_lookup (MM_PORT_SERIAL_GET_PRIVATE (self)->reply_cache, command);
+}
+
+static void
+mm_port_serial_schedule_queue_process (MMPortSerial *self, guint timeout_ms)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ if (priv->timeout_id) {
+ /* A command is already in progress */
+ return;
+ }
+
+ if (priv->queue_id) {
+ /* Already scheduled */
+ return;
+ }
+
+ if (timeout_ms)
+ priv->queue_id = g_timeout_add (timeout_ms, mm_port_serial_queue_process, self);
+ else
+ priv->queue_id = g_idle_add (mm_port_serial_queue_process, self);
+}
+
+static gsize
+real_handle_response (MMPortSerial *self,
+ GByteArray *response,
+ GError *error,
+ GCallback callback,
+ gpointer callback_data)
+{
+ MMSerialResponseFn response_callback = (MMSerialResponseFn) callback;
+
+ response_callback (self, response, error, callback_data);
+ return response->len;
+}
+
+static void
+mm_port_serial_got_response (MMPortSerial *self, GError *error)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ MMQueueData *info;
+ gsize consumed = priv->response->len;
+
+ if (priv->timeout_id) {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ if (priv->cancellable_id) {
+ g_assert (priv->cancellable != NULL);
+ g_cancellable_disconnect (priv->cancellable,
+ priv->cancellable_id);
+ priv->cancellable_id = 0;
+ }
+
+ g_clear_object (&priv->cancellable);
+
+ info = (MMQueueData *) g_queue_pop_head (priv->queue);
+ if (info) {
+ if (info->cached && !error)
+ mm_port_serial_set_cached_reply (self, info->command, priv->response);
+
+ if (info->callback) {
+ g_warn_if_fail (MM_PORT_SERIAL_GET_CLASS (self)->handle_response != NULL);
+ consumed = MM_PORT_SERIAL_GET_CLASS (self)->handle_response (self,
+ priv->response,
+ error,
+ info->callback,
+ info->user_data);
+ }
+
+ g_clear_object (&info->cancellable);
+ g_byte_array_free (info->command, TRUE);
+ g_slice_free (MMQueueData, info);
+ }
+
+ if (error)
+ g_error_free (error);
+
+ if (consumed)
+ g_byte_array_remove_range (priv->response, 0, consumed);
+ if (!g_queue_is_empty (priv->queue))
+ mm_port_serial_schedule_queue_process (self, 0);
+}
+
+static gboolean
+mm_port_serial_timed_out (gpointer data)
+{
+ MMPortSerial *self = MM_PORT_SERIAL (data);
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ GError *error;
+
+ priv->timeout_id = 0;
+
+ /* Update number of consecutive timeouts found */
+ priv->n_consecutive_timeouts++;
+
+ error = g_error_new_literal (MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_RESPONSE_TIMEOUT,
+ "Serial command timed out");
+
+ /* 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 timed out command. Not sure what to do here. */
+ mm_port_serial_got_response (self, error);
+
+ /* Emit a timed out signal, used by upper layers to identify a disconnected
+ * serial port */
+ g_signal_emit (self, signals[TIMED_OUT], 0, priv->n_consecutive_timeouts);
+
+ return FALSE;
+}
+
+static void
+port_serial_response_wait_cancelled (GCancellable *cancellable,
+ MMPortSerial *self)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ GError *error;
+
+ /* We don't want to call disconnect () while in the signal handler */
+ priv->cancellable_id = 0;
+
+ error = g_error_new_literal (MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Waiting for the reply cancelled");
+
+ /* 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. */
+ mm_port_serial_got_response (self, error);
+}
+
+static gboolean
+mm_port_serial_queue_process (gpointer data)
+{
+ MMPortSerial *self = MM_PORT_SERIAL (data);
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ MMQueueData *info;
+ GError *error = NULL;
+
+ priv->queue_id = 0;
+
+ info = (MMQueueData *) g_queue_peek_head (priv->queue);
+ if (!info)
+ return FALSE;
+
+ if (info->cached) {
+ const GByteArray *cached = mm_port_serial_get_cached_reply (self, info->command);
+
+ if (cached) {
+ /* Ensure the response array is fully empty before setting the
+ * cached response. */
+ if (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)),
+ priv->response->len);
+ g_byte_array_set_size (priv->response, 0);
+ }
+
+ g_byte_array_append (priv->response, cached->data, cached->len);
+ mm_port_serial_got_response (self, NULL);
+ return FALSE;
+ }
+ }
+
+ if (mm_port_serial_process_command (self, info, &error)) {
+ if (info->done) {
+ /* setup the cancellable so that we can stop waiting for a response */
+ if (info->cancellable) {
+ priv->cancellable = g_object_ref (info->cancellable);
+ priv->cancellable_id = (g_cancellable_connect (
+ info->cancellable,
+ (GCallback) port_serial_response_wait_cancelled,
+ self,
+ NULL));
+ if (!priv->cancellable_id) {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Won't wait for the reply");
+ mm_port_serial_got_response (self, error);
+ return FALSE;
+ }
+ }
+
+ /* If the command is finished being sent, schedule the timeout */
+ priv->timeout_id = g_timeout_add_seconds (info->timeout,
+ mm_port_serial_timed_out,
+ self);
+ } else {
+ /* Schedule the next byte of the command to be sent */
+ mm_port_serial_schedule_queue_process (self, priv->send_delay / 1000);
+ }
+ } else
+ mm_port_serial_got_response (self, error);
+
+ return FALSE;
+}
+
+static gboolean
+parse_response (MMPortSerial *self,
+ GByteArray *response,
+ GError **error)
+{
+ if (MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited)
+ MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited (self, response);
+
+ 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);
+}
+
+static gboolean
+data_available (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ MMPortSerial *self = MM_PORT_SERIAL (data);
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ char buf[SERIAL_BUF_SIZE + 1];
+ gsize bytes_read;
+ GIOStatus status;
+ MMQueueData *info;
+ const char *device;
+
+ if (condition & G_IO_HUP) {
+ device = mm_port_get_device (MM_PORT (self));
+ mm_dbg ("(%s) unexpected port hangup!", device);
+
+ if (priv->response->len)
+ g_byte_array_remove_range (priv->response, 0, priv->response->len);
+ mm_port_serial_close_force (self);
+ return FALSE;
+ }
+
+ if (condition & G_IO_ERR) {
+ if (priv->response->len)
+ g_byte_array_remove_range (priv->response, 0, priv->response->len);
+ return TRUE;
+ }
+
+ /* Don't read any input if the current command isn't done being sent yet */
+ info = g_queue_peek_nth (priv->queue, 0);
+ if (info && (info->started == TRUE) && (info->done == FALSE))
+ return TRUE;
+
+ do {
+ GError *err = NULL;
+
+ bytes_read = 0;
+ status = g_io_channel_read_chars (source, buf, SERIAL_BUF_SIZE, &bytes_read, &err);
+ if (status == G_IO_STATUS_ERROR) {
+ if (err && err->message) {
+ mm_warn ("(%s): read error: %s",
+ mm_port_get_device (MM_PORT (self)),
+ err->message);
+ }
+ g_clear_error (&err);
+ }
+
+ /* If no bytes read, just let g_io_channel wait for more data */
+ if (bytes_read == 0)
+ break;
+
+ g_assert (bytes_read > 0);
+ serial_debug (self, "<--", buf, bytes_read);
+ g_byte_array_append (priv->response, (const guint8 *) buf, bytes_read);
+
+ /* Make sure the response doesn't grow too long */
+ if ((priv->response->len > SERIAL_BUF_SIZE) && priv->spew_control) {
+ /* Notify listeners and then trim the buffer */
+ g_signal_emit (self, signals[BUFFER_FULL], 0, priv->response);
+ g_byte_array_remove_range (priv->response, 0, (SERIAL_BUF_SIZE / 2));
+ }
+
+ if (parse_response (self, priv->response, &err)) {
+ /* Reset number of consecutive timeouts only here */
+ priv->n_consecutive_timeouts = 0;
+ mm_port_serial_got_response (self, err);
+ }
+ } while ( (bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN)
+ && (priv->watch_id > 0));
+
+ return TRUE;
+}
+
+static void
+data_watch_enable (MMPortSerial *self, gboolean enable)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ if (priv->watch_id) {
+ if (enable)
+ g_warn_if_fail (priv->watch_id == 0);
+
+ g_source_remove (priv->watch_id);
+ priv->watch_id = 0;
+ }
+
+ if (enable) {
+ g_return_if_fail (priv->channel != NULL);
+ priv->watch_id = g_io_add_watch (priv->channel,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ data_available, self);
+ }
+}
+
+static void
+port_connected (MMPortSerial *self, GParamSpec *pspec, gpointer user_data)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ gboolean connected;
+
+ if (priv->fd < 0)
+ return;
+
+ /* When the port is connected, drop the serial port lock so PPP can do
+ * something with the port. When the port is disconnected, grab the lock
+ * again.
+ */
+ connected = mm_port_get_connected (MM_PORT (self));
+
+ if (ioctl (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));
+ if (!connected) {
+ // FIXME: do something here, maybe try again in a few seconds or
+ // close the port and error out?
+ }
+ }
+
+ /* When connected ignore let PPP have all the data */
+ data_watch_enable (self, !connected);
+}
+
+gboolean
+mm_port_serial_open (MMPortSerial *self, GError **error)
+{
+ MMPortSerialPrivate *priv;
+ char *devfile;
+ const char *device;
+ struct serial_struct sinfo = { 0 };
+ GTimeVal tv_start, tv_end;
+ int errno_save = 0;
+
+ g_return_val_if_fail (MM_IS_PORT_SERIAL (self), FALSE);
+
+ priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ device = mm_port_get_device (MM_PORT (self));
+
+ if (priv->forced_close) {
+ g_set_error (error,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_OPEN_FAILED,
+ "Could not open serial device %s: it has been forced close",
+ device);
+ return FALSE;
+ }
+
+ if (priv->reopen_id) {
+ g_set_error (error,
+ MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_OPEN_FAILED,
+ "Could not open serial device %s: reopen operation in progress",
+ device);
+ return FALSE;
+ }
+
+ if (priv->open_count) {
+ /* Already open */
+ goto success;
+ }
+
+ mm_dbg ("(%s) opening serial port...", device);
+
+ g_get_current_time (&tv_start);
+
+ /* Only open a new file descriptor if we weren't given one already */
+ if (priv->fd < 0) {
+ devfile = g_strdup_printf ("/dev/%s", device);
+ errno = 0;
+ priv->fd = open (devfile, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
+ errno_save = errno;
+ g_free (devfile);
+ }
+
+ if (priv->fd < 0) {
+ /* nozomi isn't ready yet when the port appears, and it'll return
+ * ENODEV when open(2) is called on it. Make sure we can handle this
+ * by returning a special error in that case.
+ */
+ g_set_error (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;
+ }
+
+ if (ioctl (priv->fd, TIOCEXCL) < 0) {
+ 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 (priv->fd, TCIOFLUSH);
+
+ if (tcgetattr (priv->fd, &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;
+ }
+
+ g_warn_if_fail (MM_PORT_SERIAL_GET_CLASS (self)->config_fd);
+ if (!MM_PORT_SERIAL_GET_CLASS (self)->config_fd (self, priv->fd, error)) {
+ mm_dbg ("(%s) failed to configure serial device", device);
+ 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.
+ */
+ if (ioctl (priv->fd, TIOCGSERIAL, &sinfo) == 0) {
+ sinfo.closing_wait = ASYNC_CLOSING_WAIT_NONE;
+ if (ioctl (priv->fd, TIOCSSERIAL, &sinfo) < 0)
+ mm_warn ("(%s): couldn't set serial port closing_wait to none: %s",
+ device, g_strerror (errno));
+ }
+
+ 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);
+
+ priv->channel = g_io_channel_unix_new (priv->fd);
+ g_io_channel_set_encoding (priv->channel, NULL, NULL);
+ data_watch_enable (self, TRUE);
+
+ g_warn_if_fail (priv->connected_id == 0);
+ priv->connected_id = g_signal_connect (self, "notify::" MM_PORT_CONNECTED,
+ G_CALLBACK (port_connected), NULL);
+
+success:
+ priv->open_count++;
+ mm_dbg ("(%s) device open count is %d (open)", device, priv->open_count);
+
+ /* Run additional port config if just opened */
+ if (priv->open_count == 1 && MM_PORT_SERIAL_GET_CLASS (self)->config)
+ MM_PORT_SERIAL_GET_CLASS (self)->config (self);
+
+ return TRUE;
+
+error:
+ mm_warn ("(%s) failed to open serial device", device);
+ close (priv->fd);
+ priv->fd = -1;
+ return FALSE;
+}
+
+gboolean
+mm_port_serial_is_open (MMPortSerial *self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (MM_IS_PORT_SERIAL (self), FALSE);
+
+ return !!MM_PORT_SERIAL_GET_PRIVATE (self)->open_count;
+}
+
+void
+mm_port_serial_close (MMPortSerial *self)
+{
+ MMPortSerialPrivate *priv;
+ const char *device;
+ int i;
+
+ g_return_if_fail (MM_IS_PORT_SERIAL (self));
+
+ priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ /* If we forced closing the port, open_count will be 0 already.
+ * Just return without issuing any warning */
+ if (priv->forced_close)
+ return;
+
+ g_return_if_fail (priv->open_count > 0);
+
+ device = mm_port_get_device (MM_PORT (self));
+
+ priv->open_count--;
+
+ mm_dbg ("(%s) device open count is %d (close)", device, priv->open_count);
+
+ if (priv->open_count > 0)
+ return;
+
+ if (priv->connected_id) {
+ g_signal_handler_disconnect (self, priv->connected_id);
+ priv->connected_id = 0;
+ }
+
+ mm_port_serial_flash_cancel (self);
+
+ if (priv->fd >= 0) {
+ GTimeVal tv_start, tv_end;
+ struct serial_struct sinfo = { 0 };
+
+ mm_dbg ("(%s) closing serial port...", device);
+
+ mm_port_set_connected (MM_PORT (self), FALSE);
+
+ /* Paranoid: ensure our closing_wait value is still set so we ignore
+ * pending data when closing the port. See GNOME bug #630670.
+ */
+ if (ioctl (priv->fd, TIOCGSERIAL, &sinfo) == 0) {
+ if (sinfo.closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+ mm_warn ("(%s): serial port closing_wait was reset!", device);
+ sinfo.closing_wait = ASYNC_CLOSING_WAIT_NONE;
+ if (ioctl (priv->fd, TIOCSSERIAL, &sinfo) < 0)
+ mm_warn ("(%s): couldn't set serial port closing_wait to none: %s",
+ device, g_strerror (errno));
+ }
+ }
+
+ g_get_current_time (&tv_start);
+
+ if (priv->channel) {
+ data_watch_enable (self, FALSE);
+ g_io_channel_shutdown (priv->channel, TRUE, NULL);
+ g_io_channel_unref (priv->channel);
+ priv->channel = NULL;
+ }
+
+ tcsetattr (priv->fd, TCSANOW, &priv->old_t);
+ tcflush (priv->fd, TCIOFLUSH);
+ close (priv->fd);
+ priv->fd = -1;
+
+ g_get_current_time (&tv_end);
+
+ mm_dbg ("(%s) serial port closed", device);
+
+ /* Some ports don't respond to data and when close is called
+ * the serial layer waits up to 30 second (closing_wait) for
+ * that data to send before giving up and returning from close().
+ * 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);
+ }
+
+ /* Clear the command queue */
+ for (i = 0; i < g_queue_get_length (priv->queue); i++) {
+ MMQueueData *item = g_queue_peek_nth (priv->queue, i);
+
+ if (item->callback) {
+ GError *error;
+ GByteArray *response;
+
+ g_warn_if_fail (MM_PORT_SERIAL_GET_CLASS (self)->handle_response != NULL);
+ error = g_error_new_literal (MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_SEND_FAILED,
+ "Serial port is now closed");
+ response = g_byte_array_sized_new (1);
+ g_byte_array_append (response, (const guint8 *) "\0", 1);
+
+ MM_PORT_SERIAL_GET_CLASS (self)->handle_response (self,
+ response,
+ error,
+ item->callback,
+ item->user_data);
+ g_error_free (error);
+ g_byte_array_free (response, TRUE);
+ }
+
+ g_clear_object (&item->cancellable);
+ g_byte_array_free (item->command, TRUE);
+ g_slice_free (MMQueueData, item);
+ }
+ g_queue_clear (priv->queue);
+
+ if (priv->timeout_id) {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ if (priv->queue_id) {
+ g_source_remove (priv->queue_id);
+ priv->queue_id = 0;
+ }
+
+ if (priv->cancellable_id) {
+ g_assert (priv->cancellable != NULL);
+ g_cancellable_disconnect (priv->cancellable,
+ priv->cancellable_id);
+ priv->cancellable_id = 0;
+ }
+
+ g_clear_object (&priv->cancellable);
+}
+
+static void
+mm_port_serial_close_force (MMPortSerial *self)
+{
+ MMPortSerialPrivate *priv;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (MM_IS_PORT_SERIAL (self));
+
+ priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ /* If already forced to close, return */
+ if (priv->forced_close)
+ return;
+
+ mm_dbg ("(%s) forced to close port", mm_port_get_device (MM_PORT (self)));
+
+ /* If already closed, done */
+ if (!priv->open_count)
+ return;
+
+ /* Force the port to close */
+ priv->open_count = 1;
+ mm_port_serial_close (self);
+
+ /* Mark as having forced the close, so that we don't warn about incorrect
+ * open counts */
+ priv->forced_close = TRUE;
+
+ /* Notify about the forced close status */
+ g_signal_emit (self, signals[FORCED_CLOSE], 0);
+}
+
+static void
+internal_queue_command (MMPortSerial *self,
+ GByteArray *command,
+ gboolean take_command,
+ gboolean cached,
+ guint32 timeout_seconds,
+ GCancellable *cancellable,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ MMQueueData *info;
+
+ g_return_if_fail (MM_IS_PORT_SERIAL (self));
+ g_return_if_fail (command != NULL);
+
+ if (priv->open_count == 0) {
+ GError *error = g_error_new_literal (MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_SEND_FAILED,
+ "Sending command failed: device is not enabled");
+ if (callback)
+ callback (self, NULL, error, user_data);
+ g_error_free (error);
+ return;
+ }
+
+ info = g_slice_new0 (MMQueueData);
+ if (take_command)
+ info->command = command;
+ else {
+ info->command = g_byte_array_sized_new (command->len);
+ g_byte_array_append (info->command, command->data, command->len);
+ }
+
+ /* Only accept about 3 seconds of EAGAIN for this command */
+ if (priv->send_delay)
+ info->eagain_count = 3000000 / priv->send_delay;
+ else
+ info->eagain_count = 1000;
+
+ info->cached = cached;
+ info->timeout = timeout_seconds;
+ info->cancellable = (cancellable ? g_object_ref (cancellable) : NULL);
+ info->callback = (GCallback) callback;
+ info->user_data = user_data;
+
+ /* Clear the cached value for this command if not asking for cached value */
+ if (!cached)
+ mm_port_serial_set_cached_reply (self, info->command, NULL);
+
+ g_queue_push_tail (priv->queue, info);
+
+ if (g_queue_get_length (priv->queue) == 1)
+ mm_port_serial_schedule_queue_process (self, 0);
+}
+
+void
+mm_port_serial_queue_command (MMPortSerial *self,
+ GByteArray *command,
+ gboolean take_command,
+ guint32 timeout_seconds,
+ GCancellable *cancellable,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ internal_queue_command (self, command, take_command, FALSE, timeout_seconds, cancellable, callback, user_data);
+}
+
+void
+mm_port_serial_queue_command_cached (MMPortSerial *self,
+ GByteArray *command,
+ gboolean take_command,
+ guint32 timeout_seconds,
+ GCancellable *cancellable,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ internal_queue_command (self, command, take_command, TRUE, timeout_seconds, cancellable, callback, user_data);
+}
+
+typedef struct {
+ MMPortSerial *port;
+ guint initial_open_count;
+ MMSerialReopenFn callback;
+ gpointer user_data;
+} ReopenInfo;
+
+static void
+port_serial_reopen_cancel (MMPortSerial *self)
+{
+ MMPortSerialPrivate *priv;
+
+ g_return_if_fail (MM_IS_PORT_SERIAL (self));
+
+ priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ if (priv->reopen_id > 0) {
+ g_source_remove (priv->reopen_id);
+ priv->reopen_id = 0;
+ }
+}
+
+static gboolean
+reopen_do (gpointer data)
+{
+ ReopenInfo *info = (ReopenInfo *) data;
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (info->port);
+ GError *error = NULL;
+ guint i;
+
+ priv->reopen_id = 0;
+
+ for (i = 0; i < info->initial_open_count; i++) {
+ if (!mm_port_serial_open (info->port, &error)) {
+ g_prefix_error (&error, "Couldn't reopen port (%u): ", i);
+ break;
+ }
+ }
+
+ info->callback (info->port, error, info->user_data);
+ if (error)
+ g_error_free (error);
+ g_slice_free (ReopenInfo, info);
+ return FALSE;
+}
+
+gboolean
+mm_port_serial_reopen (MMPortSerial *self,
+ guint32 reopen_time,
+ MMSerialReopenFn callback,
+ gpointer user_data)
+{
+ ReopenInfo *info;
+ MMPortSerialPrivate *priv;
+ guint i;
+
+ g_return_val_if_fail (MM_IS_PORT_SERIAL (self), FALSE);
+ priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ if (priv->forced_close) {
+ GError *error;
+
+ error = g_error_new_literal (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Serial port has been forced close.");
+ callback (self, error, user_data);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (priv->reopen_id > 0) {
+ GError *error;
+
+ error = g_error_new_literal (MM_CORE_ERROR,
+ MM_CORE_ERROR_IN_PROGRESS,
+ "Modem is already being reopened.");
+ callback (self, error, user_data);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ info = g_slice_new0 (ReopenInfo);
+ info->port = self;
+ info->callback = callback;
+ info->user_data = user_data;
+
+ priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ info->initial_open_count = priv->open_count;
+
+ mm_dbg ("(%s) reopening port (%u)",
+ mm_port_get_device (MM_PORT (self)),
+ info->initial_open_count);
+
+ for (i = 0; i < info->initial_open_count; i++)
+ mm_port_serial_close (self);
+
+ if (reopen_time > 0)
+ priv->reopen_id = g_timeout_add (reopen_time, reopen_do, info);
+ else
+ priv->reopen_id = g_idle_add (reopen_do, info);
+
+ return TRUE;
+}
+
+static gboolean
+get_speed (MMPortSerial *self, speed_t *speed, GError **error)
+{
+ struct termios options;
+
+ memset (&options, 0, sizeof (struct termios));
+ if (tcgetattr (MM_PORT_SERIAL_GET_PRIVATE (self)->fd, &options) != 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "%s: tcgetattr() error %d",
+ __func__, errno);
+ return FALSE;
+ }
+
+ *speed = cfgetospeed (&options);
+ return TRUE;
+}
+
+static gboolean
+set_speed (MMPortSerial *self, speed_t speed, GError **error)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+ struct termios options;
+ int fd, count = 4;
+ gboolean success = FALSE;
+
+ fd = MM_PORT_SERIAL_GET_PRIVATE (self)->fd;
+
+ memset (&options, 0, sizeof (struct termios));
+ if (tcgetattr (fd, &options) != 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "%s: tcgetattr() error %d",
+ __func__, errno);
+ return FALSE;
+ }
+
+ cfsetispeed (&options, speed);
+ cfsetospeed (&options, speed);
+ options.c_cflag |= (CLOCAL | CREAD);
+
+ /* Configure flow control as well here */
+ if (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;
+}
+
+typedef struct {
+ MMPortSerial *port;
+ speed_t current_speed;
+ MMSerialFlashFn callback;
+ gpointer user_data;
+} FlashInfo;
+
+static gboolean
+flash_do (gpointer data)
+{
+ FlashInfo *info = (FlashInfo *) data;
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (info->port);
+ GError *error = NULL;
+
+ priv->flash_id = 0;
+
+ if (priv->flash_ok) {
+ if (info->current_speed) {
+ if (!set_speed (info->port, info->current_speed, &error))
+ g_assert (error);
+ } else {
+ error = g_error_new_literal (MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_FLASH_FAILED,
+ "Failed to retrieve current speed");
+ }
+ }
+
+ info->callback (info->port, error, info->user_data);
+ g_clear_error (&error);
+ g_slice_free (FlashInfo, info);
+ return FALSE;
+}
+
+gboolean
+mm_port_serial_flash (MMPortSerial *self,
+ guint32 flash_time,
+ gboolean ignore_errors,
+ MMSerialFlashFn callback,
+ gpointer user_data)
+{
+ FlashInfo *info = NULL;
+ MMPortSerialPrivate *priv;
+ GError *error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (MM_IS_PORT_SERIAL (self), FALSE);
+ g_return_val_if_fail (callback != NULL, FALSE);
+
+ priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ if (!mm_port_serial_is_open (self)) {
+ error = g_error_new_literal (MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_NOT_OPEN,
+ "The serial port is not open.");
+ goto error;
+ }
+
+ if (priv->flash_id > 0) {
+ error = g_error_new_literal (MM_CORE_ERROR,
+ MM_CORE_ERROR_IN_PROGRESS,
+ "Modem is already being flashed.");
+ goto error;
+ }
+
+ info = g_slice_new0 (FlashInfo);
+ info->port = self;
+ info->callback = callback;
+ info->user_data = user_data;
+
+ if (priv->flash_ok) {
+ /* Grab current speed so we can reset it after flashing */
+ success = get_speed (self, &info->current_speed, &error);
+ if (!success && !ignore_errors)
+ goto error;
+ g_clear_error (&error);
+
+ success = set_speed (self, B0, &error);
+ if (!success && !ignore_errors)
+ goto error;
+ g_clear_error (&error);
+
+ priv->flash_id = g_timeout_add (flash_time, flash_do, info);
+ } else
+ priv->flash_id = g_idle_add (flash_do, info);
+
+ return TRUE;
+
+error:
+ callback (self, error, user_data);
+ g_clear_error (&error);
+ if (info)
+ g_slice_free (FlashInfo, info);
+ return FALSE;
+}
+
+void
+mm_port_serial_flash_cancel (MMPortSerial *self)
+{
+ MMPortSerialPrivate *priv;
+
+ g_return_if_fail (MM_IS_PORT_SERIAL (self));
+
+ priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ if (priv->flash_id > 0) {
+ g_source_remove (priv->flash_id);
+ priv->flash_id = 0;
+ }
+}
+
+gboolean
+mm_port_serial_get_flash_ok (MMPortSerial *self)
+{
+ g_return_val_if_fail (MM_IS_PORT_SERIAL (self), TRUE);
+
+ return MM_PORT_SERIAL_GET_PRIVATE (self)->flash_ok;
+}
+
+/*****************************************************************************/
+
+MMPortSerial *
+mm_port_serial_new (const char *name, MMPortType ptype)
+{
+ return MM_PORT_SERIAL (g_object_new (MM_TYPE_PORT_SERIAL,
+ MM_PORT_DEVICE, name,
+ MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY,
+ MM_PORT_TYPE, ptype,
+ NULL));
+}
+
+static gboolean
+ba_equal (gconstpointer v1, gconstpointer v2)
+{
+ const GByteArray *a = v1;
+ const GByteArray *b = v2;
+
+ if (!a && b)
+ return -1;
+ else if (a && !b)
+ return 1;
+ else if (!a && !b)
+ return 0;
+
+ g_assert (a && b);
+ if (a->len < b->len)
+ return -1;
+ else if (a->len > b->len)
+ return 1;
+
+ g_assert (a->len == b->len);
+ return !memcmp (a->data, b->data, a->len);
+}
+
+static guint
+ba_hash (gconstpointer v)
+{
+ /* 31 bit hash function */
+ const GByteArray *array = v;
+ guint32 i, h = (const signed char) array->data[0];
+
+ for (i = 1; i < array->len; i++)
+ h = (h << 5) - h + (const signed char) array->data[i];
+
+ return h;
+}
+
+static void
+ba_free (gpointer v)
+{
+ g_byte_array_free ((GByteArray *) v, TRUE);
+}
+
+static void
+mm_port_serial_init (MMPortSerial *self)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ priv->reply_cache = g_hash_table_new_full (ba_hash, ba_equal, ba_free, ba_free);
+
+ priv->fd = -1;
+ priv->baud = 57600;
+ priv->bits = 8;
+ priv->parity = 'n';
+ priv->stopbits = 1;
+ priv->send_delay = 1000;
+
+ priv->queue = g_queue_new ();
+ priv->response = g_byte_array_sized_new (500);
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_FD:
+ priv->fd = g_value_get_int (value);
+ break;
+ case PROP_BAUD:
+ priv->baud = g_value_get_uint (value);
+ break;
+ case PROP_BITS:
+ priv->bits = g_value_get_uint (value);
+ break;
+ case PROP_PARITY:
+ priv->parity = g_value_get_schar (value);
+ break;
+ case PROP_STOPBITS:
+ priv->stopbits = g_value_get_uint (value);
+ break;
+ case PROP_SEND_DELAY:
+ priv->send_delay = g_value_get_uint64 (value);
+ break;
+ case PROP_SPEW_CONTROL:
+ priv->spew_control = g_value_get_boolean (value);
+ break;
+ case PROP_RTS_CTS:
+ priv->rts_cts = g_value_get_boolean (value);
+ break;
+ case PROP_FLASH_OK:
+ priv->flash_ok = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_FD:
+ g_value_set_int (value, priv->fd);
+ break;
+ case PROP_BAUD:
+ g_value_set_uint (value, priv->baud);
+ break;
+ case PROP_BITS:
+ g_value_set_uint (value, priv->bits);
+ break;
+ case PROP_PARITY:
+ g_value_set_schar (value, priv->parity);
+ break;
+ case PROP_STOPBITS:
+ g_value_set_uint (value, priv->stopbits);
+ break;
+ case PROP_SEND_DELAY:
+ g_value_set_uint64 (value, priv->send_delay);
+ break;
+ case PROP_SPEW_CONTROL:
+ g_value_set_boolean (value, priv->spew_control);
+ break;
+ case PROP_RTS_CTS:
+ g_value_set_boolean (value, priv->rts_cts);
+ break;
+ case PROP_FLASH_OK:
+ g_value_set_boolean (value, priv->flash_ok);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (object);
+
+ if (priv->timeout_id) {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ mm_port_serial_close_force (MM_PORT_SERIAL (object));
+
+ port_serial_reopen_cancel (MM_PORT_SERIAL (object));
+ mm_port_serial_flash_cancel (MM_PORT_SERIAL (object));
+
+ G_OBJECT_CLASS (mm_port_serial_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ MMPortSerial *self = MM_PORT_SERIAL (object);
+ MMPortSerialPrivate *priv = MM_PORT_SERIAL_GET_PRIVATE (self);
+
+ g_hash_table_destroy (priv->reply_cache);
+ g_byte_array_free (priv->response, TRUE);
+ g_queue_free (priv->queue);
+
+ G_OBJECT_CLASS (mm_port_serial_parent_class)->finalize (object);
+}
+
+static void
+mm_port_serial_class_init (MMPortSerialClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMPortSerialPrivate));
+
+ /* Virtual methods */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ klass->config_fd = real_config_fd;
+ klass->handle_response = real_handle_response;
+
+ /* Properties */
+ g_object_class_install_property
+ (object_class, PROP_FD,
+ g_param_spec_int (MM_PORT_SERIAL_FD,
+ "File descriptor",
+ "File descriptor",
+ -1, G_MAXINT, -1,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
+ (object_class, PROP_BAUD,
+ g_param_spec_uint (MM_PORT_SERIAL_BAUD,
+ "Baud",
+ "Baud rate",
+ 0, G_MAXUINT, 57600,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_BITS,
+ g_param_spec_uint (MM_PORT_SERIAL_BITS,
+ "Bits",
+ "Bits",
+ 5, 8, 8,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_PARITY,
+ g_param_spec_char (MM_PORT_SERIAL_PARITY,
+ "Parity",
+ "Parity",
+ 'E', 'o', 'n',
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_STOPBITS,
+ g_param_spec_uint (MM_PORT_SERIAL_STOPBITS,
+ "Stopbits",
+ "Stopbits",
+ 1, 2, 1,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_SEND_DELAY,
+ g_param_spec_uint64 (MM_PORT_SERIAL_SEND_DELAY,
+ "SendDelay",
+ "Send delay for each byte in microseconds",
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_SPEW_CONTROL,
+ g_param_spec_boolean (MM_PORT_SERIAL_SPEW_CONTROL,
+ "SpewControl",
+ "Spew control",
+ FALSE,
+ 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));
+
+ /* Signals */
+ signals[BUFFER_FULL] =
+ g_signal_new ("buffer-full",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMPortSerialClass, buffer_full),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ signals[TIMED_OUT] =
+ g_signal_new ("timed-out",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMPortSerialClass, timed_out),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ signals[FORCED_CLOSE] =
+ g_signal_new ("forced-close",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (MMPortSerialClass, forced_close),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+}