aboutsummaryrefslogtreecommitdiff
path: root/src/mm-serial-port.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-serial-port.c')
-rw-r--r--src/mm-serial-port.c1107
1 files changed, 1107 insertions, 0 deletions
diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c
new file mode 100644
index 00000000..e825d597
--- /dev/null
+++ b/src/mm-serial-port.c
@@ -0,0 +1,1107 @@
+/* -*- 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 Red Hat, Inc.
+ */
+
+#define _GNU_SOURCE /* for strcasestr() */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <termio.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 "mm-serial-port.h"
+#include "mm-errors.h"
+#include "mm-options.h"
+
+static gboolean mm_serial_port_queue_process (gpointer data);
+
+G_DEFINE_TYPE (MMSerialPort, mm_serial_port, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ PROP_BAUD,
+ PROP_BITS,
+ PROP_PARITY,
+ PROP_STOPBITS,
+ PROP_SEND_DELAY,
+ PROP_CARRIER_DETECT,
+
+ LAST_PROP
+};
+
+#define SERIAL_BUF_SIZE 2048
+
+#define MM_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_SERIAL_PORT, MMSerialPortPrivate))
+
+typedef struct {
+ int fd;
+ GHashTable *reply_cache;
+ GIOChannel *channel;
+ GQueue *queue;
+ GString *command;
+ GString *response;
+
+ /* Response parser data */
+ MMSerialResponseParserFn response_parser_fn;
+ gpointer response_parser_user_data;
+ GDestroyNotify response_parser_notify;
+ GSList *unsolicited_msg_handlers;
+
+ struct termios old_t;
+
+ char *device;
+ guint baud;
+ guint bits;
+ char parity;
+ guint stopbits;
+ guint64 send_delay;
+ gboolean carrier_detect;
+
+ guint queue_schedule;
+ guint watch_id;
+ guint timeout_id;
+} MMSerialPortPrivate;
+
+typedef struct {
+ GRegex *regex;
+ MMSerialUnsolicitedMsgFn callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+} MMUnsolicitedMsgHandler;
+
+const char *
+mm_serial_port_get_device (MMSerialPort *self)
+{
+ g_return_val_if_fail (MM_IS_SERIAL_PORT (self), NULL);
+
+ return MM_SERIAL_PORT_GET_PRIVATE (self)->device;
+}
+
+static void
+mm_serial_port_set_cached_reply (MMSerialPort *self,
+ const char *command,
+ const char *reply)
+{
+ if (reply)
+ g_hash_table_insert (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache,
+ g_strdup (command),
+ g_strdup (reply));
+ else
+ g_hash_table_remove (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command);
+}
+
+static const char *
+mm_serial_port_get_cached_reply (MMSerialPort *self,
+ const char *command)
+{
+ return (char *) g_hash_table_lookup (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command);
+}
+
+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:
+ g_warning ("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:
+ g_warning ("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:
+ g_warning ("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:
+ g_warning ("Invalid stop bits (%d). Valid values are 1 and 2)", i);
+ stopbits = 0;
+ }
+
+ return stopbits;
+}
+
+static gboolean
+config_fd (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ struct termio stbuf;
+ 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);
+
+ ioctl (priv->fd, TCGETA, &stbuf);
+
+ stbuf.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY | IGNPAR );
+ 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;
+
+ stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | CLOCAL | PARENB);
+ stbuf.c_cflag |= (speed | bits | CREAD | 0 | parity | stopbits);
+
+ if (ioctl (priv->fd, TCSETA, &stbuf) < 0) {
+ g_warning ("(%s) cannot control device (errno %d)", priv->device, errno);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+serial_debug (const char *prefix, const char *buf, int len)
+{
+ static GString *debug = NULL;
+ const char *s;
+
+ if (!mm_options_debug ())
+ return;
+
+ if (len < 0)
+ len = strlen (buf);
+
+ if (!debug)
+ debug = g_string_sized_new (256);
+
+ g_string_append (debug, prefix);
+ g_string_append (debug, " '");
+
+ s = buf;
+ while (len--) {
+ if (g_ascii_isprint (*s))
+ g_string_append_c (debug, *s);
+ else if (*s == '\r')
+ g_string_append (debug, "<CR>");
+ else if (*s == '\n')
+ g_string_append (debug, "<LF>");
+ else
+ g_string_append_printf (debug, "\\%d", *s);
+
+ s++;
+ }
+
+ g_string_append_c (debug, '\'');
+ g_debug ("%s", debug->str);
+ g_string_truncate (debug, 0);
+}
+
+static gboolean
+mm_serial_port_send_command (MMSerialPort *self,
+ const char *command,
+ GError **error)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ const char *s;
+ int status;
+ int eagain_count = 1000;
+
+ if (priv->fd < 0) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED,
+ "%s", "Sending command failed: device is not enabled");
+ return FALSE;
+ }
+
+ if (mm_serial_port_is_connected (self)) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED,
+ "%s", "Sending command failed: device is connected");
+ return FALSE;
+ }
+
+ g_string_truncate (priv->command, g_str_has_prefix (command, "AT") ? 0 : 2);
+ g_string_append (priv->command, command);
+
+ if (command[strlen (command)] != '\r')
+ g_string_append_c (priv->command, '\r');
+
+ serial_debug ("-->", priv->command->str, -1);
+
+ s = priv->command->str;
+ while (*s) {
+ status = write (priv->fd, s, 1);
+ if (status < 0) {
+ if (errno == EAGAIN) {
+ eagain_count--;
+ if (eagain_count <= 0) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED,
+ "Sending command failed: '%s'", strerror (errno));
+ break;
+ }
+ } else {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED,
+ "Sending command failed: '%s'", strerror (errno));
+ break;
+ }
+ } else
+ s++;
+
+ if (priv->send_delay)
+ usleep (priv->send_delay);
+ }
+
+ return *s == '\0';
+}
+
+typedef struct {
+ char *command;
+ MMSerialResponseFn callback;
+ gpointer user_data;
+ guint32 timeout;
+ gboolean cached;
+} MMQueueData;
+
+static void
+mm_serial_port_schedule_queue_process (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ GSource *source;
+
+ if (priv->queue_schedule)
+ /* Already scheduled */
+ return;
+
+ source = g_idle_source_new ();
+ g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (mm_serial_port_queue_process), G_OBJECT (self)));
+ g_source_attach (source, NULL);
+ priv->queue_schedule = g_source_get_id (source);
+ g_source_unref (source);
+}
+
+static void
+mm_serial_port_got_response (MMSerialPort *self, GError *error)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ MMQueueData *info;
+
+ if (priv->timeout_id)
+ g_source_remove (priv->timeout_id);
+
+ info = (MMQueueData *) g_queue_pop_head (priv->queue);
+ if (info) {
+ if (info->cached && !error)
+ mm_serial_port_set_cached_reply (self, info->command, priv->response->str);
+
+ if (info->callback)
+ info->callback (self, priv->response, error, info->user_data);
+
+ g_free (info->command);
+ g_slice_free (MMQueueData, info);
+ }
+
+ if (error)
+ g_error_free (error);
+
+ g_string_truncate (priv->response, 0);
+ if (!g_queue_is_empty (priv->queue))
+ mm_serial_port_schedule_queue_process (self);
+}
+
+static gboolean
+mm_serial_port_timed_out (gpointer data)
+{
+ MMSerialPort *self = MM_SERIAL_PORT (data);
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ GError *error;
+
+ priv->timeout_id = 0;
+
+ error = g_error_new_literal (MM_SERIAL_ERROR,
+ MM_SERIAL_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_serial_port_got_response (self, error);
+
+ return FALSE;
+}
+
+static gboolean
+mm_serial_port_queue_process (gpointer data)
+{
+ MMSerialPort *self = MM_SERIAL_PORT (data);
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ MMQueueData *info;
+ GError *error = NULL;
+
+ priv->queue_schedule = 0;
+
+ info = (MMQueueData *) g_queue_peek_head (priv->queue);
+ if (!info)
+ return FALSE;
+
+ if (info->cached) {
+ const char *cached = mm_serial_port_get_cached_reply (self, info->command);
+
+ if (cached) {
+ g_string_append (priv->response, cached);
+ mm_serial_port_got_response (self, NULL);
+ return FALSE;
+ }
+ }
+
+ if (mm_serial_port_send_command (self, info->command, &error)) {
+ GSource *source;
+
+ source = g_timeout_source_new (info->timeout);
+ g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (mm_serial_port_timed_out), G_OBJECT (self)));
+ g_source_attach (source, NULL);
+ priv->timeout_id = g_source_get_id (source);
+ g_source_unref (source);
+ } else {
+ mm_serial_port_got_response (self, error);
+ }
+
+ return FALSE;
+}
+
+void
+mm_serial_port_add_unsolicited_msg_handler (MMSerialPort *self,
+ GRegex *regex,
+ MMSerialUnsolicitedMsgFn callback,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ MMUnsolicitedMsgHandler *handler;
+ MMSerialPortPrivate *priv;
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+ g_return_if_fail (regex != NULL);
+
+ handler = g_slice_new (MMUnsolicitedMsgHandler);
+ handler->regex = g_regex_ref (regex);
+ handler->callback = callback;
+ handler->user_data = user_data;
+ handler->notify = notify;
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ priv->unsolicited_msg_handlers = g_slist_append (priv->unsolicited_msg_handlers, handler);
+}
+
+void
+mm_serial_port_set_response_parser (MMSerialPort *self,
+ MMSerialResponseParserFn fn,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+
+ if (priv->response_parser_notify)
+ priv->response_parser_notify (priv->response_parser_user_data);
+
+ priv->response_parser_fn = fn;
+ priv->response_parser_user_data = user_data;
+ priv->response_parser_notify = notify;
+}
+
+static gboolean
+remove_eval_cb (const GMatchInfo *match_info,
+ GString *result,
+ gpointer user_data)
+{
+ int *result_len = (int *) user_data;
+ int start;
+ int end;
+
+ if (g_match_info_fetch_pos (match_info, 0, &start, &end))
+ *result_len -= (end - start);
+
+ return FALSE;
+}
+
+static void
+parse_unsolicited_messages (MMSerialPort *self,
+ GString *response)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ GSList *iter;
+
+ for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) {
+ MMUnsolicitedMsgHandler *handler = (MMUnsolicitedMsgHandler *) iter->data;
+ GMatchInfo *match_info;
+ gboolean matches;
+
+ matches = g_regex_match_full (handler->regex, response->str, response->len, 0, 0, &match_info, NULL);
+ if (handler->callback) {
+ while (g_match_info_matches (match_info)) {
+ handler->callback (self, match_info, handler->user_data);
+ g_match_info_next (match_info, NULL);
+ }
+ }
+
+ g_match_info_free (match_info);
+
+ if (matches) {
+ /* Remove matches */
+ char *str;
+ int result_len = response->len;
+
+ str = g_regex_replace_eval (handler->regex, response->str, response->len, 0, 0,
+ remove_eval_cb, &result_len, NULL);
+
+ g_string_truncate (response, 0);
+ g_string_append_len (response, str, result_len);
+ g_free (str);
+ }
+ }
+}
+
+static gboolean
+parse_response (MMSerialPort *self,
+ GString *response,
+ GError **error)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE);
+
+ parse_unsolicited_messages (self, response);
+
+ return priv->response_parser_fn (priv->response_parser_user_data, response, error);
+}
+
+static gboolean
+data_available (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ MMSerialPort *self = MM_SERIAL_PORT (data);
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ char buf[SERIAL_BUF_SIZE + 1];
+ gsize bytes_read;
+ GIOStatus status;
+
+ if (condition & G_IO_HUP) {
+ g_string_truncate (priv->response, 0);
+ mm_serial_port_close (self);
+ return FALSE;
+ }
+
+ if (condition & G_IO_ERR) {
+ g_string_truncate (priv->response, 0);
+ return TRUE;
+ }
+
+ do {
+ GError *err = NULL;
+
+ status = g_io_channel_read_chars (source, buf, SERIAL_BUF_SIZE, &bytes_read, &err);
+ if (status == G_IO_STATUS_ERROR) {
+ g_warning ("%s", err->message);
+ g_error_free (err);
+ err = NULL;
+ }
+
+ if (bytes_read > 0) {
+ serial_debug ("<--", buf, bytes_read);
+ g_string_append_len (priv->response, buf, bytes_read);
+ }
+
+ /* Make sure the string doesn't grow too long */
+ if (priv->response->len > SERIAL_BUF_SIZE) {
+ g_warning ("%s (%s): response buffer filled before repsonse received",
+ G_STRFUNC, mm_serial_port_get_device (self));
+ g_string_erase (priv->response, 0, (SERIAL_BUF_SIZE / 2));
+ }
+
+ if (parse_response (self, priv->response, &err))
+ mm_serial_port_got_response (self, err);
+ } while (bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN);
+
+ return TRUE;
+}
+
+gboolean
+mm_serial_port_open (MMSerialPort *self, GError **error)
+{
+ MMSerialPortPrivate *priv;
+ char *devfile;
+
+ g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE);
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ if (priv->fd >= 0)
+ /* Already open */
+ return TRUE;
+
+ g_debug ("(%s) opening serial device...", priv->device);
+ devfile = g_strdup_printf ("/dev/%s", priv->device);
+ priv->fd = open (devfile, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
+ g_free (devfile);
+
+ if (priv->fd < 0) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED,
+ "Could not open serial device %s: %s", priv->device, strerror (errno));
+ return FALSE;
+ }
+
+ if (ioctl (priv->fd, TCGETA, &priv->old_t) < 0) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED,
+ "Could not open serial device %s: %s", priv->device, strerror (errno));
+ close (priv->fd);
+ priv->fd = -1;
+ return FALSE;
+ }
+
+ if (!config_fd (self)) {
+ g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED,
+ "Could not open serial device %s: %s", priv->device, strerror (errno));
+ close (priv->fd);
+ priv->fd = -1;
+ return FALSE;
+ }
+
+ priv->channel = g_io_channel_unix_new (priv->fd);
+ priv->watch_id = g_io_add_watch (priv->channel,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ data_available, self);
+
+ return TRUE;
+}
+
+void
+mm_serial_port_close (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv;
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ if (priv->fd >= 0) {
+ g_message ("Closing device '%s'", priv->device);
+
+ if (priv->channel) {
+ g_source_remove (priv->watch_id);
+ g_io_channel_shutdown (priv->channel, TRUE, NULL);
+ g_io_channel_unref (priv->channel);
+ priv->channel = NULL;
+ }
+
+ ioctl (priv->fd, TCSETA, &priv->old_t);
+ close (priv->fd);
+ priv->fd = -1;
+ }
+}
+
+static void
+internal_queue_command (MMSerialPort *self,
+ const char *command,
+ gboolean cached,
+ guint32 timeout_seconds,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+ MMQueueData *info;
+
+ g_return_if_fail (MM_IS_SERIAL_PORT (self));
+ g_return_if_fail (command != NULL);
+
+ info = g_slice_new0 (MMQueueData);
+ info->command = g_strdup (command);
+ info->cached = cached;
+ info->timeout = timeout_seconds * 1000;
+ info->callback = callback;
+ info->user_data = user_data;
+
+ /* Clear the cached value for this command if not asking for cached value */
+ if (!cached)
+ mm_serial_port_set_cached_reply (self, command, NULL);
+
+ g_queue_push_tail (priv->queue, info);
+
+ if (g_queue_get_length (priv->queue) == 1)
+ mm_serial_port_schedule_queue_process (self);
+}
+
+void
+mm_serial_port_queue_command (MMSerialPort *self,
+ const char *command,
+ guint32 timeout_seconds,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ internal_queue_command (self, command, FALSE, timeout_seconds, callback, user_data);
+}
+
+void
+mm_serial_port_queue_command_cached (MMSerialPort *self,
+ const char *command,
+ guint32 timeout_seconds,
+ MMSerialResponseFn callback,
+ gpointer user_data)
+{
+ internal_queue_command (self, command, TRUE, timeout_seconds, callback, user_data);
+}
+
+typedef struct {
+ MMSerialPort *port;
+ speed_t current_speed;
+ MMSerialFlashFn callback;
+ gpointer user_data;
+} FlashInfo;
+
+static speed_t
+get_speed (MMSerialPort *self)
+{
+ struct termios options;
+
+ tcgetattr (MM_SERIAL_PORT_GET_PRIVATE (self)->fd, &options);
+
+ return cfgetospeed (&options);
+}
+
+static void
+set_speed (MMSerialPort *self, speed_t speed)
+{
+ struct termios options;
+ int fd;
+
+ fd = MM_SERIAL_PORT_GET_PRIVATE (self)->fd;
+ tcgetattr (fd, &options);
+
+ cfsetispeed (&options, speed);
+ cfsetospeed (&options, speed);
+
+ options.c_cflag |= (CLOCAL | CREAD);
+ tcsetattr (fd, TCSANOW, &options);
+}
+
+static void
+flash_done (gpointer data)
+{
+ FlashInfo *info = (FlashInfo *) data;
+
+ info->callback (info->port, info->user_data);
+
+ g_slice_free (FlashInfo, info);
+}
+
+static gboolean
+flash_do (gpointer data)
+{
+ FlashInfo *info = (FlashInfo *) data;
+
+ set_speed (info->port, info->current_speed);
+
+ return FALSE;
+}
+
+guint
+mm_serial_port_flash (MMSerialPort *self,
+ guint32 flash_time,
+ MMSerialFlashFn callback,
+ gpointer user_data)
+{
+ FlashInfo *info;
+ guint id;
+
+ g_return_val_if_fail (MM_IS_SERIAL_PORT (self), 0);
+ g_return_val_if_fail (callback != NULL, 0);
+
+ info = g_slice_new0 (FlashInfo);
+ info->port = self;
+ info->current_speed = get_speed (self);
+ info->callback = callback;
+ info->user_data = user_data;
+
+ set_speed (self, B0);
+
+ id = g_timeout_add_full (G_PRIORITY_DEFAULT,
+ flash_time,
+ flash_do,
+ info,
+ flash_done);
+
+ return id;
+}
+
+gboolean
+mm_serial_port_is_connected (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv;
+ int mcs = 0;
+
+ g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE);
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ if (!priv->carrier_detect)
+ return FALSE;
+
+ if (priv->fd < 0)
+ return FALSE;
+
+ if (ioctl (priv->fd, TIOCMGET, &mcs) < 0)
+ return FALSE;
+
+ return mcs & TIOCM_CAR ? TRUE : FALSE;
+}
+
+/*****************************************************************************/
+
+MMSerialPort *
+mm_serial_port_new (const char *name)
+{
+ return MM_SERIAL_PORT (g_object_new (MM_TYPE_SERIAL_PORT,
+ MM_SERIAL_PORT_DEVICE, name,
+ NULL));
+}
+
+static void
+mm_serial_port_init (MMSerialPort *self)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ priv->reply_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ priv->fd = -1;
+ priv->baud = 57600;
+ priv->bits = 8;
+ priv->parity = 'n';
+ priv->stopbits = 1;
+ priv->send_delay = 1000;
+ priv->carrier_detect = TRUE;
+
+ priv->queue = g_queue_new ();
+ priv->command = g_string_new_len ("AT", SERIAL_BUF_SIZE);
+ priv->response = g_string_sized_new (SERIAL_BUF_SIZE);
+}
+
+static GObject*
+constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ MMSerialPortPrivate *priv;
+
+ object = G_OBJECT_CLASS (mm_serial_port_parent_class)->constructor (type,
+ n_construct_params,
+ construct_params);
+ if (!object)
+ return NULL;
+
+ priv = MM_SERIAL_PORT_GET_PRIVATE (object);
+
+ if (!priv->device) {
+ g_warning ("No device provided");
+ g_object_unref (object);
+ return NULL;
+ }
+
+ return object;
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ /* Construct only */
+ priv->device = g_path_get_basename (g_value_get_string (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_char (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_CARRIER_DETECT:
+ priv->carrier_detect = 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)
+{
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ g_value_set_string (value, priv->device);
+ 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_char (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_CARRIER_DETECT:
+ g_value_set_boolean (value, priv->carrier_detect);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ MMSerialPort *self = MM_SERIAL_PORT (object);
+ MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
+
+ mm_serial_port_close (self);
+
+ g_hash_table_destroy (priv->reply_cache);
+ g_queue_free (priv->queue);
+ g_string_free (priv->command, TRUE);
+ g_string_free (priv->response, TRUE);
+ g_free (priv->device);
+
+ while (priv->unsolicited_msg_handlers) {
+ MMUnsolicitedMsgHandler *handler = (MMUnsolicitedMsgHandler *) priv->unsolicited_msg_handlers->data;
+
+ if (handler->notify)
+ handler->notify (handler->user_data);
+
+ g_regex_unref (handler->regex);
+ g_slice_free (MMUnsolicitedMsgHandler, handler);
+ priv->unsolicited_msg_handlers = g_slist_delete_link (priv->unsolicited_msg_handlers,
+ priv->unsolicited_msg_handlers);
+ }
+
+ if (priv->response_parser_notify)
+ priv->response_parser_notify (priv->response_parser_user_data);
+
+ G_OBJECT_CLASS (mm_serial_port_parent_class)->finalize (object);
+}
+
+static void
+mm_serial_port_class_init (MMSerialPortClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMSerialPortPrivate));
+
+ /* Virtual methods */
+ object_class->constructor = constructor;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->finalize = finalize;
+
+ /* Properties */
+ g_object_class_install_property
+ (object_class, PROP_DEVICE,
+ g_param_spec_string (MM_SERIAL_PORT_DEVICE,
+ "Device",
+ "Serial device",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property
+ (object_class, PROP_BAUD,
+ g_param_spec_uint (MM_SERIAL_PORT_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_SERIAL_PORT_BITS,
+ "Bits",
+ "Bits",
+ 5, 8, 8,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_PARITY,
+ g_param_spec_char (MM_SERIAL_PORT_PARITY,
+ "Parity",
+ "Parity",
+ 'E', 'o', 'n',
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_STOPBITS,
+ g_param_spec_uint (MM_SERIAL_PORT_STOPBITS,
+ "Stopbits",
+ "Stopbits",
+ 1, 2, 1,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_SEND_DELAY,
+ g_param_spec_uint64 (MM_SERIAL_PORT_SEND_DELAY,
+ "SendDelay",
+ "Send delay",
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_CARRIER_DETECT,
+ g_param_spec_boolean (MM_SERIAL_PORT_CARRIER_DETECT,
+ "CarrierDetect",
+ "Has carrier detect",
+ TRUE,
+ G_PARAM_READWRITE));
+}