2182 lines
67 KiB
C
2182 lines
67 KiB
C
/* -*- 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 <gio/gunixsocketaddress.h>
|
|
|
|
#include <ModemManager.h>
|
|
#include <mm-errors-types.h>
|
|
|
|
#include "mm-port-serial.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,
|
|
guint timeout_ms);
|
|
static void port_serial_close_force (MMPortSerial *self);
|
|
static void port_serial_reopen_cancel (MMPortSerial *self);
|
|
static void port_serial_set_cached_reply (MMPortSerial *self,
|
|
const GByteArray *command,
|
|
const GByteArray *response);
|
|
|
|
G_DEFINE_TYPE (MMPortSerial, mm_port_serial, MM_TYPE_PORT)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_BAUD,
|
|
PROP_BITS,
|
|
PROP_PARITY,
|
|
PROP_STOPBITS,
|
|
PROP_FLOW_CONTROL,
|
|
PROP_SEND_DELAY,
|
|
PROP_FD,
|
|
PROP_SPEW_CONTROL,
|
|
PROP_FLASH_OK,
|
|
|
|
LAST_PROP
|
|
};
|
|
|
|
enum {
|
|
BUFFER_FULL,
|
|
FORCED_CLOSE,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
#define SERIAL_BUF_SIZE 2048
|
|
|
|
struct _MMPortSerialPrivate {
|
|
guint32 open_count;
|
|
gboolean forced_close;
|
|
int fd;
|
|
GHashTable *reply_cache;
|
|
GQueue *queue;
|
|
GByteArray *response;
|
|
|
|
/* For real ports, iochannel, and we implement the eagain limit */
|
|
GIOChannel *iochannel;
|
|
guint iochannel_id;
|
|
|
|
/* For unix-socket based ports, socket */
|
|
GSocket *socket;
|
|
GSource *socket_source;
|
|
|
|
|
|
guint baud;
|
|
guint bits;
|
|
char parity;
|
|
guint stopbits;
|
|
MMFlowControl flow_control;
|
|
guint64 send_delay;
|
|
gboolean spew_control;
|
|
gboolean flash_ok;
|
|
|
|
guint queue_id;
|
|
guint timeout_id;
|
|
|
|
GCancellable *cancellable;
|
|
gulong cancellable_id;
|
|
|
|
guint n_consecutive_timeouts;
|
|
|
|
guint connected_id;
|
|
|
|
GTask *flash_task;
|
|
GTask *reopen_task;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/* Command */
|
|
|
|
typedef struct {
|
|
GByteArray *command;
|
|
guint32 timeout;
|
|
gboolean allow_cached;
|
|
guint32 eagain_count;
|
|
|
|
guint32 idx;
|
|
gboolean started;
|
|
gboolean done;
|
|
} CommandContext;
|
|
|
|
static void
|
|
command_context_free (CommandContext *ctx)
|
|
{
|
|
g_byte_array_unref (ctx->command);
|
|
g_slice_free (CommandContext, ctx);
|
|
}
|
|
|
|
GByteArray *
|
|
mm_port_serial_command_finish (MMPortSerial *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
void
|
|
mm_port_serial_command (MMPortSerial *self,
|
|
GByteArray *command,
|
|
guint32 timeout_seconds,
|
|
gboolean allow_cached,
|
|
gboolean run_next,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
CommandContext *ctx;
|
|
GTask *task;
|
|
|
|
g_return_if_fail (MM_IS_PORT_SERIAL (self));
|
|
g_return_if_fail (command != NULL);
|
|
|
|
/* Setup command context */
|
|
ctx = g_slice_new0 (CommandContext);
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)command_context_free);
|
|
|
|
ctx->command = g_byte_array_ref (command);
|
|
ctx->allow_cached = allow_cached;
|
|
ctx->timeout = timeout_seconds;
|
|
|
|
/* Only accept about 3 seconds of EAGAIN for this command */
|
|
if (self->priv->send_delay && mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY)
|
|
ctx->eagain_count = 3000000 / self->priv->send_delay;
|
|
else
|
|
ctx->eagain_count = 1000;
|
|
|
|
if (self->priv->open_count == 0) {
|
|
g_task_return_new_error (task,
|
|
MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_SEND_FAILED,
|
|
"Sending command failed: device is not open");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Clear the cached value for this command if not asking for cached value */
|
|
if (!allow_cached)
|
|
port_serial_set_cached_reply (self, ctx->command, NULL);
|
|
|
|
/* 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, task);
|
|
else
|
|
g_queue_push_tail (self->priv->queue, task);
|
|
|
|
if (g_queue_get_length (self->priv->queue) == 1)
|
|
port_serial_schedule_queue_process (self, 0);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
parse_baudrate (guint baudrate_num,
|
|
guint *out_baudrate_speed)
|
|
{
|
|
guint speed;
|
|
|
|
switch (baudrate_num) {
|
|
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 230400:
|
|
speed = B230400;
|
|
break;
|
|
case 460800:
|
|
speed = B460800;
|
|
break;
|
|
case 921600:
|
|
speed = B921600;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
if (out_baudrate_speed)
|
|
*out_baudrate_speed = speed;
|
|
return TRUE;
|
|
}
|
|
|
|
static int
|
|
parse_bits (guint i)
|
|
{
|
|
int bits = -1;
|
|
|
|
switch (i) {
|
|
case 5:
|
|
bits = CS5;
|
|
break;
|
|
case 6:
|
|
bits = CS6;
|
|
break;
|
|
case 7:
|
|
bits = CS7;
|
|
break;
|
|
case 8:
|
|
bits = CS8;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
return bits;
|
|
}
|
|
|
|
static int
|
|
parse_parity (char c)
|
|
{
|
|
int parity = -1;
|
|
|
|
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_assert_not_reached ();
|
|
}
|
|
|
|
return parity;
|
|
}
|
|
|
|
static int
|
|
parse_stopbits (guint i)
|
|
{
|
|
int stopbits = -1;
|
|
|
|
switch (i) {
|
|
case 1:
|
|
stopbits = 0;
|
|
break;
|
|
case 2:
|
|
stopbits = CSTOPB;
|
|
break;
|
|
default:
|
|
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;
|
|
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;
|
|
|
|
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_obj_warn (self, "error getting serial port attributes: %s", g_strerror (errno));
|
|
|
|
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 | ECHO | ECHOE | ECHONL);
|
|
stbuf.c_cc[VMIN] = 1;
|
|
stbuf.c_cc[VTIME] = 0;
|
|
stbuf.c_cc[VEOF] = 1;
|
|
|
|
/* Ignore parity/framing errors */
|
|
stbuf.c_iflag |= IGNPAR;
|
|
|
|
/* 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) {
|
|
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 (self->priv->flow_control != MM_FLOW_CONTROL_UNKNOWN) {
|
|
gchar *str;
|
|
|
|
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");
|
|
|
|
set_flow_control_termios (self, self->priv->flow_control, &stbuf);
|
|
|
|
return internal_tcsetattr (self, fd, &stbuf, error);
|
|
}
|
|
|
|
static void
|
|
serial_debug (MMPortSerial *self,
|
|
const gchar *prefix,
|
|
const gchar *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
|
|
port_serial_process_command (MMPortSerial *self,
|
|
CommandContext *ctx,
|
|
GError **error)
|
|
{
|
|
const gchar *p;
|
|
gsize written;
|
|
gssize send_len;
|
|
|
|
if (self->priv->iochannel == NULL && self->priv->socket == NULL) {
|
|
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 (ctx->started == FALSE) {
|
|
ctx->started = TRUE;
|
|
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) {
|
|
/* Send the whole command in one write */
|
|
send_len = (gssize)ctx->command->len;
|
|
p = (gchar *)ctx->command->data;
|
|
} else {
|
|
/* Send just one byte of the command */
|
|
send_len = 1;
|
|
p = (gchar *)&ctx->command->data[ctx->idx];
|
|
}
|
|
|
|
/* GIOChannel based setup */
|
|
if (self->priv->iochannel) {
|
|
GIOStatus write_status;
|
|
|
|
/* Send N bytes of the command */
|
|
write_status = g_io_channel_write_chars (self->priv->iochannel, p, send_len, &written, error);
|
|
switch (write_status) {
|
|
case G_IO_STATUS_ERROR:
|
|
g_prefix_error (error, "Sending command failed: ");
|
|
return FALSE;
|
|
|
|
case G_IO_STATUS_EOF:
|
|
/* We shouldn't get EOF when writing */
|
|
g_assert_not_reached ();
|
|
break;
|
|
|
|
case G_IO_STATUS_NORMAL:
|
|
if (written > 0) {
|
|
ctx->idx += written;
|
|
break;
|
|
}
|
|
/* 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
|
|
* EAGAIN; just retry in this case. */
|
|
ctx->eagain_count--;
|
|
if (ctx->eagain_count <= 0) {
|
|
/* If we reach the limit of EAGAIN errors, treat as a timeout error. */
|
|
self->priv->n_consecutive_timeouts++;
|
|
g_signal_emit_by_name (self, MM_PORT_SIGNAL_TIMED_OUT, self->priv->n_consecutive_timeouts);
|
|
|
|
g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
|
|
"Sending command failed: '%s'", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
/* Socket based setup */
|
|
else if (self->priv->socket) {
|
|
GError *inner_error = NULL;
|
|
gssize bytes_sent;
|
|
|
|
/* Send N bytes of the command */
|
|
bytes_sent = g_socket_send (self->priv->socket, p, send_len, NULL, &inner_error);
|
|
if (bytes_sent < 0) {
|
|
/* Non-EWOULDBLOCK error? */
|
|
if (!g_error_matches (inner_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
|
|
g_propagate_error (error, inner_error);
|
|
g_prefix_error (error, "Sending command failed: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* We're in a non-blocking socket and therefore we're up to receive
|
|
* EWOULDBLOCK; just retry in this case. */
|
|
|
|
g_error_free (inner_error);
|
|
|
|
ctx->eagain_count--;
|
|
if (ctx->eagain_count <= 0) {
|
|
/* If we reach the limit of EAGAIN errors, treat as a timeout error. */
|
|
self->priv->n_consecutive_timeouts++;
|
|
g_signal_emit_by_name (self, MM_PORT_SIGNAL_TIMED_OUT, self->priv->n_consecutive_timeouts);
|
|
g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
|
|
"Sending command failed: '%s'", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Just keep on, will retry... */
|
|
written = 0;
|
|
} else
|
|
written = bytes_sent;
|
|
|
|
ctx->idx += written;
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
if (ctx->idx >= ctx->command->len)
|
|
ctx->done = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
port_serial_set_cached_reply (MMPortSerial *self,
|
|
const GByteArray *command,
|
|
const GByteArray *response)
|
|
{
|
|
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 (self->priv->reply_cache, cmd_copy, rsp_copy);
|
|
} else
|
|
g_hash_table_remove (self->priv->reply_cache, command);
|
|
}
|
|
|
|
static const GByteArray *
|
|
port_serial_get_cached_reply (MMPortSerial *self,
|
|
GByteArray *command)
|
|
{
|
|
return (const GByteArray *)g_hash_table_lookup (self->priv->reply_cache, command);
|
|
}
|
|
|
|
static void
|
|
port_serial_schedule_queue_process (MMPortSerial *self, guint timeout_ms)
|
|
{
|
|
if (self->priv->timeout_id) {
|
|
/* A command is already in progress */
|
|
return;
|
|
}
|
|
|
|
if (self->priv->queue_id) {
|
|
/* Already scheduled */
|
|
return;
|
|
}
|
|
|
|
if (timeout_ms)
|
|
self->priv->queue_id = g_timeout_add (timeout_ms, port_serial_queue_process, self);
|
|
else
|
|
self->priv->queue_id = g_idle_add (port_serial_queue_process, self);
|
|
}
|
|
|
|
static void
|
|
port_serial_got_response (MMPortSerial *self,
|
|
GByteArray *parsed_response,
|
|
GError *error)
|
|
{
|
|
/* 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);
|
|
self->priv->timeout_id = 0;
|
|
}
|
|
|
|
if (self->priv->cancellable_id) {
|
|
g_assert (self->priv->cancellable != NULL);
|
|
g_cancellable_disconnect (self->priv->cancellable,
|
|
self->priv->cancellable_id);
|
|
self->priv->cancellable_id = 0;
|
|
}
|
|
|
|
g_clear_object (&self->priv->cancellable);
|
|
|
|
/* 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);
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_queue_pop_head (self->priv->queue);
|
|
if (task) {
|
|
/* Complete the command context with the appropriate result */
|
|
if (error) {
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
} else {
|
|
CommandContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
if (ctx->allow_cached)
|
|
port_serial_set_cached_reply (self, ctx->command, parsed_response);
|
|
g_task_return_pointer (task,
|
|
g_byte_array_ref (parsed_response),
|
|
(GDestroyNotify) g_byte_array_unref);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
if (!g_queue_is_empty (self->priv->queue))
|
|
port_serial_schedule_queue_process (self, 0);
|
|
}
|
|
g_object_unref (self);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
static gboolean
|
|
port_serial_timed_out (gpointer data)
|
|
{
|
|
MMPortSerial *self = MM_PORT_SERIAL (data);
|
|
GError *error;
|
|
|
|
self->priv->timeout_id = 0;
|
|
|
|
/* Update number of consecutive timeouts found */
|
|
self->priv->n_consecutive_timeouts++;
|
|
|
|
/* 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. */
|
|
error = g_error_new_literal (MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_RESPONSE_TIMEOUT,
|
|
"Serial command timed out");
|
|
|
|
/* Make sure we have a valid reference when emitting the signal */
|
|
g_object_ref (self);
|
|
{
|
|
port_serial_got_response (self, NULL, error);
|
|
|
|
/* Emit a timed out signal, used by upper layers to identify a disconnected
|
|
* serial port */
|
|
g_signal_emit_by_name (self, MM_PORT_SIGNAL_TIMED_OUT, self->priv->n_consecutive_timeouts);
|
|
}
|
|
g_object_unref (self);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
port_serial_response_wait_cancelled (GCancellable *cancellable,
|
|
MMPortSerial *self)
|
|
{
|
|
GError *error;
|
|
|
|
/* We don't want to call disconnect () while in the signal handler */
|
|
self->priv->cancellable_id = 0;
|
|
|
|
/* 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 (G_IO_ERROR, G_IO_ERROR_CANCELLED,
|
|
"Waiting for the reply cancelled");
|
|
/* Note: may complete last operation and unref the MMPortSerial */
|
|
port_serial_got_response (self, NULL, error);
|
|
}
|
|
|
|
static gboolean
|
|
port_serial_queue_process (gpointer data)
|
|
{
|
|
MMPortSerial *self = MM_PORT_SERIAL (data);
|
|
CommandContext *ctx;
|
|
GTask *task;
|
|
GCancellable *cancellable;
|
|
GError *error = NULL;
|
|
|
|
self->priv->queue_id = 0;
|
|
|
|
task = g_queue_peek_head (self->priv->queue);
|
|
if (!task)
|
|
return G_SOURCE_REMOVE;
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->allow_cached) {
|
|
const GByteArray *cached;
|
|
|
|
cached = port_serial_get_cached_reply (self, ctx->command);
|
|
if (cached) {
|
|
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 */
|
|
}
|
|
|
|
/* If error, report it */
|
|
if (!port_serial_process_command (self, ctx, &error)) {
|
|
/* Note: may complete last operation and unref the MMPortSerial */
|
|
port_serial_got_response (self, NULL, error);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* Schedule the next byte of the command to be sent */
|
|
if (!ctx->done) {
|
|
port_serial_schedule_queue_process (self,
|
|
(mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY ?
|
|
self->priv->send_delay / 1000 :
|
|
0));
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* Setup the cancellable so that we can stop waiting for a response */
|
|
cancellable = g_task_get_cancellable (task);
|
|
if (cancellable) {
|
|
gulong cancellable_id;
|
|
|
|
self->priv->cancellable = g_object_ref (cancellable);
|
|
|
|
/* 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 (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 G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
parse_response_buffer (MMPortSerial *self)
|
|
{
|
|
GError *error = NULL;
|
|
GByteArray *parsed_response = NULL;
|
|
|
|
/* 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);
|
|
break;
|
|
case MM_PORT_SERIAL_RESPONSE_NONE:
|
|
/* Nothing to do this time */
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
common_input_available (MMPortSerial *self,
|
|
GIOCondition condition)
|
|
{
|
|
char buf[SERIAL_BUF_SIZE + 1];
|
|
gsize bytes_read;
|
|
GIOStatus status = G_IO_STATUS_NORMAL;
|
|
CommandContext *ctx;
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
gboolean iterate = TRUE;
|
|
gboolean keep_source = G_SOURCE_CONTINUE;
|
|
|
|
if (condition & G_IO_HUP) {
|
|
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);
|
|
/* The completion of the commands with an error 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 close runs. */
|
|
g_object_ref (self);
|
|
{
|
|
port_serial_close_force (self);
|
|
}
|
|
g_object_unref (self);
|
|
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 G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
/* Don't read any input if the current command isn't done being sent yet */
|
|
task = g_queue_peek_nth (self->priv->queue, 0);
|
|
ctx = task ? g_task_get_task_data (task) : NULL;
|
|
if (ctx && (ctx->started == TRUE) && (ctx->done == FALSE))
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
while (iterate) {
|
|
bytes_read = 0;
|
|
|
|
if (self->priv->iochannel) {
|
|
status = g_io_channel_read_chars (self->priv->iochannel,
|
|
buf,
|
|
SERIAL_BUF_SIZE,
|
|
&bytes_read,
|
|
&error);
|
|
if (status == G_IO_STATUS_ERROR) {
|
|
if (error)
|
|
mm_obj_warn (self, "read error: %s", error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
} else if (self->priv->socket) {
|
|
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_obj_warn (self, "receive error: %s", error->message);
|
|
g_clear_error (&error);
|
|
} 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;
|
|
|
|
g_assert (bytes_read > 0);
|
|
serial_debug (self, "<--", buf, bytes_read);
|
|
g_byte_array_append (self->priv->response, (const guint8 *) buf, bytes_read);
|
|
|
|
/* 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);
|
|
{
|
|
/* Make sure the response doesn't grow too long */
|
|
if ((self->priv->response->len > SERIAL_BUF_SIZE) && self->priv->spew_control) {
|
|
/* Notify listeners and then trim the buffer */
|
|
g_signal_emit (self, signals[BUFFER_FULL], 0, self->priv->response);
|
|
g_byte_array_remove_range (self->priv->response, 0, (SERIAL_BUF_SIZE / 2));
|
|
}
|
|
|
|
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));
|
|
}
|
|
g_object_unref (self);
|
|
}
|
|
|
|
return keep_source;
|
|
}
|
|
|
|
static gboolean
|
|
iochannel_input_available (GIOChannel *iochannel,
|
|
GIOCondition condition,
|
|
gpointer data)
|
|
{
|
|
return common_input_available (MM_PORT_SERIAL (data), condition);
|
|
}
|
|
|
|
static gboolean
|
|
socket_input_available (GSocket *socket,
|
|
GIOCondition condition,
|
|
gpointer data)
|
|
{
|
|
return common_input_available (MM_PORT_SERIAL (data), condition);
|
|
}
|
|
|
|
static void
|
|
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;
|
|
}
|
|
|
|
if (self->priv->socket_source) {
|
|
if (enable)
|
|
g_warn_if_fail (self->priv->socket_source == NULL);
|
|
g_source_destroy (self->priv->socket_source);
|
|
g_source_unref (self->priv->socket_source);
|
|
self->priv->socket_source = NULL;
|
|
}
|
|
|
|
if (enable) {
|
|
if (self->priv->iochannel) {
|
|
self->priv->iochannel_id = g_io_add_watch (self->priv->iochannel,
|
|
G_IO_IN | G_IO_ERR | G_IO_HUP,
|
|
iochannel_input_available,
|
|
self);
|
|
} else if (self->priv->socket) {
|
|
self->priv->socket_source = g_socket_create_source (self->priv->socket,
|
|
G_IO_IN | G_IO_ERR | G_IO_HUP,
|
|
NULL);
|
|
g_source_set_callback (self->priv->socket_source,
|
|
(GSourceFunc)socket_input_available,
|
|
self,
|
|
NULL);
|
|
g_source_attach (self->priv->socket_source, NULL);
|
|
}
|
|
else
|
|
g_warn_if_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
port_connected (MMPortSerial *self, GParamSpec *pspec, gpointer user_data)
|
|
{
|
|
gboolean connected;
|
|
|
|
if (!self->priv->iochannel && !self->priv->socket)
|
|
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 (self->priv->fd >= 0 && ioctl (self->priv->fd, (connected ? TIOCNXCL : TIOCEXCL)) < 0) {
|
|
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?
|
|
}
|
|
}
|
|
|
|
/* When connected ignore let PPP have all the data */
|
|
data_watch_enable (self, !connected);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_serial_open (MMPortSerial *self, GError **error)
|
|
{
|
|
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);
|
|
|
|
device = mm_port_get_device (MM_PORT (self));
|
|
|
|
if (self->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 (self->priv->reopen_task) {
|
|
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 (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_obj_dbg (self, "opening serial port...");
|
|
|
|
g_get_current_time (&tv_start);
|
|
|
|
/* Non-socket setup needs the fd open */
|
|
if (mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_UNIX) {
|
|
/* Only open a new file descriptor if we weren't given one already */
|
|
if (self->priv->fd < 0) {
|
|
devfile = g_strdup_printf ("/dev/%s", device);
|
|
errno = 0;
|
|
self->priv->fd = open (devfile, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
|
|
errno_save = errno;
|
|
g_free (devfile);
|
|
}
|
|
|
|
if (self->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));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Serial port specific setup */
|
|
if (mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY) {
|
|
/* Try to lock serial device */
|
|
if (ioctl (self->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));
|
|
goto error;
|
|
}
|
|
|
|
/* Flush any waiting IO */
|
|
tcflush (self->priv->fd, TCIOFLUSH);
|
|
|
|
/* 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 (self->priv->fd, TIOCGSERIAL, &sinfo) == 0) {
|
|
sinfo.closing_wait = ASYNC_CLOSING_WAIT_NONE;
|
|
if (ioctl (self->priv->fd, TIOCSSERIAL, &sinfo) < 0)
|
|
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_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_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 */
|
|
self->priv->iochannel = g_io_channel_unix_new (self->priv->fd);
|
|
|
|
/* We don't want UTF-8 encoding, we're playing with raw binary data */
|
|
g_io_channel_set_encoding (self->priv->iochannel, NULL, NULL);
|
|
|
|
/* We don't want to get the channel buffered */
|
|
g_io_channel_set_buffered (self->priv->iochannel, FALSE);
|
|
|
|
/* We don't want to get blocked while writing stuff */
|
|
if (!g_io_channel_set_flags (self->priv->iochannel,
|
|
G_IO_FLAG_NONBLOCK,
|
|
error)) {
|
|
g_prefix_error (error, "Cannot set non-blocking channel: ");
|
|
goto error;
|
|
}
|
|
} else {
|
|
GSocketAddress *address;
|
|
|
|
/* Create new GSocket */
|
|
self->priv->socket = g_socket_new (G_SOCKET_FAMILY_UNIX,
|
|
G_SOCKET_TYPE_STREAM,
|
|
G_SOCKET_PROTOCOL_DEFAULT,
|
|
error);
|
|
if (!self->priv->socket) {
|
|
g_prefix_error (error, "Cannot create socket: ");
|
|
goto error;
|
|
}
|
|
|
|
/* Non-blocking socket */
|
|
g_socket_set_blocking (self->priv->socket, FALSE);
|
|
|
|
/* By default, abstract socket */
|
|
address = (g_unix_socket_address_new_with_type (
|
|
device,
|
|
-1,
|
|
(g_str_has_prefix (device, "abstract:") ?
|
|
G_UNIX_SOCKET_ADDRESS_ABSTRACT :
|
|
G_UNIX_SOCKET_ADDRESS_PATH)));
|
|
|
|
/* Connect to address */
|
|
if (!g_socket_connect (self->priv->socket,
|
|
address,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error (error, "Cannot connect socket: ");
|
|
g_object_unref (address);
|
|
goto error;
|
|
}
|
|
g_object_unref (address);
|
|
}
|
|
|
|
/* Reading watch enable */
|
|
data_watch_enable (self, TRUE);
|
|
|
|
g_warn_if_fail (self->priv->connected_id == 0);
|
|
self->priv->connected_id = g_signal_connect (self,
|
|
"notify::" MM_PORT_CONNECTED,
|
|
G_CALLBACK (port_connected),
|
|
NULL);
|
|
|
|
success:
|
|
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)
|
|
MM_PORT_SERIAL_GET_CLASS (self)->config (self);
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
mm_obj_warn (self, "failed to open serial device");
|
|
|
|
if (self->priv->iochannel) {
|
|
g_io_channel_unref (self->priv->iochannel);
|
|
self->priv->iochannel = NULL;
|
|
}
|
|
|
|
if (self->priv->socket) {
|
|
g_socket_close (self->priv->socket, NULL);
|
|
g_object_unref (self->priv->socket);
|
|
self->priv->socket = NULL;
|
|
}
|
|
|
|
if (self->priv->fd >= 0) {
|
|
close (self->priv->fd);
|
|
self->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 !!self->priv->open_count;
|
|
}
|
|
|
|
static void
|
|
_close_internal (MMPortSerial *self, gboolean force)
|
|
{
|
|
guint i;
|
|
|
|
g_return_if_fail (MM_IS_PORT_SERIAL (self));
|
|
|
|
if (force)
|
|
self->priv->open_count = 0;
|
|
else {
|
|
g_return_if_fail (self->priv->open_count > 0);
|
|
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) {
|
|
/* 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;
|
|
}
|
|
|
|
mm_port_serial_flash_cancel (self);
|
|
|
|
if (self->priv->iochannel || self->priv->socket) {
|
|
GTimeVal tv_start, tv_end;
|
|
struct serial_struct sinfo = { 0 };
|
|
|
|
mm_obj_dbg (self, "closing serial port...");
|
|
|
|
mm_port_set_connected (MM_PORT (self), FALSE);
|
|
|
|
g_get_current_time (&tv_start);
|
|
|
|
/* Serial port specific setup */
|
|
if (self->priv->fd >= 0 && mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY) {
|
|
/* Paranoid: ensure our closing_wait value is still set so we ignore
|
|
* pending data when closing the port. See GNOME bug #630670.
|
|
*/
|
|
if (ioctl (self->priv->fd, TIOCGSERIAL, &sinfo) == 0) {
|
|
if (sinfo.closing_wait != ASYNC_CLOSING_WAIT_NONE) {
|
|
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_obj_warn (self, "couldn't set serial port closing_wait to none: %s", g_strerror (errno));
|
|
}
|
|
}
|
|
|
|
tcflush (self->priv->fd, TCIOFLUSH);
|
|
}
|
|
|
|
/* Destroy channel */
|
|
if (self->priv->iochannel) {
|
|
data_watch_enable (self, FALSE);
|
|
/* 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;
|
|
}
|
|
|
|
/* Close fd, if any */
|
|
if (self->priv->fd >= 0) {
|
|
close (self->priv->fd);
|
|
self->priv->fd = -1;
|
|
}
|
|
|
|
/* Destroy socket */
|
|
if (self->priv->socket) {
|
|
data_watch_enable (self, FALSE);
|
|
g_socket_close (self->priv->socket, NULL);
|
|
g_object_unref (self->priv->socket);
|
|
self->priv->socket = NULL;
|
|
}
|
|
|
|
g_get_current_time (&tv_end);
|
|
|
|
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
|
|
* 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_obj_warn (self, "close blocked by driver for more than 7 seconds!");
|
|
}
|
|
|
|
/* Clear the command queue */
|
|
for (i = 0; i < g_queue_get_length (self->priv->queue); i++) {
|
|
GTask *task;
|
|
|
|
task = g_queue_peek_nth (self->priv->queue, i);
|
|
g_task_return_new_error (task,
|
|
MM_SERIAL_ERROR,
|
|
MM_SERIAL_ERROR_SEND_FAILED,
|
|
"Serial port is now closed");
|
|
g_object_unref (task);
|
|
}
|
|
g_queue_clear (self->priv->queue);
|
|
|
|
if (self->priv->timeout_id) {
|
|
g_source_remove (self->priv->timeout_id);
|
|
self->priv->timeout_id = 0;
|
|
}
|
|
|
|
if (self->priv->queue_id) {
|
|
g_source_remove (self->priv->queue_id);
|
|
self->priv->queue_id = 0;
|
|
}
|
|
|
|
if (self->priv->cancellable_id) {
|
|
g_assert (self->priv->cancellable != NULL);
|
|
g_cancellable_disconnect (self->priv->cancellable,
|
|
self->priv->cancellable_id);
|
|
self->priv->cancellable_id = 0;
|
|
}
|
|
|
|
g_clear_object (&self->priv->cancellable);
|
|
}
|
|
|
|
void
|
|
mm_port_serial_close (MMPortSerial *self)
|
|
{
|
|
g_return_if_fail (MM_IS_PORT_SERIAL (self));
|
|
|
|
if (!self->priv->forced_close)
|
|
_close_internal (self, FALSE);
|
|
}
|
|
|
|
static void
|
|
port_serial_close_force (MMPortSerial *self)
|
|
{
|
|
g_return_if_fail (MM_IS_PORT_SERIAL (self));
|
|
|
|
/* If already forced to close, return */
|
|
if (self->priv->forced_close)
|
|
return;
|
|
|
|
mm_obj_dbg (self, "forced to close port");
|
|
|
|
/* Mark as having forced the close, so that we don't warn about incorrect
|
|
* open counts */
|
|
self->priv->forced_close = TRUE;
|
|
|
|
/* Cancel port reopening if one is running */
|
|
port_serial_reopen_cancel (self);
|
|
|
|
/* If already closed, done */
|
|
if (self->priv->open_count > 0) {
|
|
_close_internal (self, TRUE);
|
|
|
|
/* Notify about the forced close status */
|
|
g_signal_emit (self, signals[FORCED_CLOSE], 0);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Reopen */
|
|
|
|
typedef struct {
|
|
guint initial_open_count;
|
|
guint reopen_id;
|
|
} ReopenContext;
|
|
|
|
static void
|
|
reopen_context_free (ReopenContext *ctx)
|
|
{
|
|
if (ctx->reopen_id)
|
|
g_source_remove (ctx->reopen_id);
|
|
g_slice_free (ReopenContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_serial_reopen_finish (MMPortSerial *port,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
port_serial_reopen_cancel (MMPortSerial *self)
|
|
{
|
|
GTask *task;
|
|
|
|
if (!self->priv->reopen_task)
|
|
return;
|
|
|
|
/* Recover task */
|
|
task = self->priv->reopen_task;
|
|
self->priv->reopen_task = NULL;
|
|
|
|
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 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 (self, &error)) {
|
|
g_prefix_error (&error, "Couldn't reopen port (%u): ", i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 G_SOURCE_REMOVE;
|
|
}
|
|
|
|
void
|
|
mm_port_serial_reopen (MMPortSerial *self,
|
|
guint32 reopen_time,
|
|
GAsyncReadyCallback callback,
|
|
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->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_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_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_obj_dbg (self, "reopening port (%u)", ctx->initial_open_count);
|
|
|
|
for (i = 0; i < ctx->initial_open_count; i++)
|
|
mm_port_serial_close (self);
|
|
|
|
if (reopen_time > 0)
|
|
ctx->reopen_id = g_timeout_add (reopen_time, (GSourceFunc)reopen_do, self);
|
|
else
|
|
ctx->reopen_id = g_idle_add ((GSourceFunc)reopen_do, self);
|
|
|
|
/* Store context in private info */
|
|
self->priv->reopen_task = task;
|
|
}
|
|
|
|
static gboolean
|
|
get_speed (MMPortSerial *self, speed_t *speed, GError **error)
|
|
{
|
|
struct termios options;
|
|
|
|
g_assert (self->priv->fd >= 0);
|
|
|
|
memset (&options, 0, sizeof (struct termios));
|
|
if (tcgetattr (self->priv->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)
|
|
{
|
|
struct termios options;
|
|
|
|
g_assert (self->priv->fd >= 0);
|
|
|
|
memset (&options, 0, sizeof (struct termios));
|
|
if (tcgetattr (self->priv->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);
|
|
|
|
return internal_tcsetattr (self, self->priv->fd, &options, error);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Flash */
|
|
|
|
typedef struct {
|
|
speed_t current_speed;
|
|
guint flash_id;
|
|
} FlashContext;
|
|
|
|
static void
|
|
flash_context_free (FlashContext *ctx)
|
|
{
|
|
if (ctx->flash_id)
|
|
g_source_remove (ctx->flash_id);
|
|
g_slice_free (FlashContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_port_serial_flash_finish (MMPortSerial *port,
|
|
GAsyncResult *res,
|
|
GError **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;
|
|
|
|
/* Do nothing if there is no flash task */
|
|
if (!self->priv->flash_task)
|
|
return;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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 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 (self, ctx->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");
|
|
}
|
|
}
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
void
|
|
mm_port_serial_flash (MMPortSerial *self,
|
|
guint32 flash_time,
|
|
gboolean ignore_errors,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
FlashContext *ctx;
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
gboolean success;
|
|
|
|
g_return_if_fail (MM_IS_PORT_SERIAL (self));
|
|
|
|
/* Setup context */
|
|
ctx = g_slice_new0 (FlashContext);
|
|
|
|
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_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_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_task = task;
|
|
ctx->flash_id = g_idle_add ((GSourceFunc)flash_do, self);
|
|
return;
|
|
}
|
|
|
|
/* Grab current speed so we can reset it after flashing */
|
|
success = get_speed (self, &ctx->current_speed, &error);
|
|
if (!success && !ignore_errors) {
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
g_clear_error (&error);
|
|
|
|
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)
|
|
{
|
|
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_unref ((GByteArray *) v);
|
|
}
|
|
|
|
static void
|
|
mm_port_serial_init (MMPortSerial *self)
|
|
{
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_PORT_SERIAL, MMPortSerialPrivate);
|
|
|
|
self->priv->reply_cache = g_hash_table_new_full (ba_hash, ba_equal, ba_free, ba_free);
|
|
|
|
self->priv->fd = -1;
|
|
self->priv->baud = 57600;
|
|
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 ();
|
|
self->priv->response = g_byte_array_sized_new (500);
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMPortSerial *self = MM_PORT_SERIAL (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FD:
|
|
self->priv->fd = g_value_get_int (value);
|
|
break;
|
|
case PROP_BAUD:
|
|
self->priv->baud = g_value_get_uint (value);
|
|
break;
|
|
case PROP_BITS:
|
|
self->priv->bits = g_value_get_uint (value);
|
|
break;
|
|
case PROP_PARITY:
|
|
self->priv->parity = g_value_get_schar (value);
|
|
break;
|
|
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_FLASH_OK:
|
|
self->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)
|
|
{
|
|
MMPortSerial *self = MM_PORT_SERIAL (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FD:
|
|
g_value_set_int (value, self->priv->fd);
|
|
break;
|
|
case PROP_BAUD:
|
|
g_value_set_uint (value, self->priv->baud);
|
|
break;
|
|
case PROP_BITS:
|
|
g_value_set_uint (value, self->priv->bits);
|
|
break;
|
|
case PROP_PARITY:
|
|
g_value_set_schar (value, self->priv->parity);
|
|
break;
|
|
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_FLASH_OK:
|
|
g_value_set_boolean (value, self->priv->flash_ok);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMPortSerial *self = MM_PORT_SERIAL (object);
|
|
|
|
port_serial_close_force (MM_PORT_SERIAL (object));
|
|
mm_port_serial_flash_cancel (MM_PORT_SERIAL (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);
|
|
|
|
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);
|
|
g_queue_free (self->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->finalize = finalize;
|
|
|
|
klass->config_fd = real_config_fd;
|
|
|
|
/* 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_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",
|
|
"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_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));
|
|
|
|
/* 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[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);
|
|
}
|