port-serial: allow ports based on Unix sockets

This commit is contained in:
Aleksander Morgado
2013-11-22 16:58:25 +01:00
parent 65f87561c5
commit 84ab92d85a
3 changed files with 273 additions and 97 deletions

View File

@@ -503,7 +503,9 @@ MMPortSerialAt *
mm_port_serial_at_new (const char *name, mm_port_serial_at_new (const char *name,
MMPortSubsys subsys) MMPortSubsys subsys)
{ {
g_return_val_if_fail (subsys == MM_PORT_SUBSYS_TTY || subsys == MM_PORT_SUBSYS_USB, NULL); g_return_val_if_fail (subsys == MM_PORT_SUBSYS_TTY ||
subsys == MM_PORT_SUBSYS_USB ||
subsys == MM_PORT_SUBSYS_UNIX, NULL);
return MM_PORT_SERIAL_AT (g_object_new (MM_TYPE_PORT_SERIAL_AT, return MM_PORT_SERIAL_AT (g_object_new (MM_TYPE_PORT_SERIAL_AT,
MM_PORT_DEVICE, name, MM_PORT_DEVICE, name,

View File

@@ -28,6 +28,8 @@
#include <string.h> #include <string.h>
#include <linux/serial.h> #include <linux/serial.h>
#include <gio/gunixsocketaddress.h>
#include <ModemManager.h> #include <ModemManager.h>
#include <mm-errors-types.h> #include <mm-errors-types.h>
@@ -77,10 +79,17 @@ struct _MMPortSerialPrivate {
gboolean forced_close; gboolean forced_close;
int fd; int fd;
GHashTable *reply_cache; GHashTable *reply_cache;
GIOChannel *iochannel;
GQueue *queue; GQueue *queue;
GByteArray *response; 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;
struct termios old_t; struct termios old_t;
guint baud; guint baud;
@@ -93,7 +102,6 @@ struct _MMPortSerialPrivate {
gboolean flash_ok; gboolean flash_ok;
guint queue_id; guint queue_id;
guint watch_id;
guint timeout_id; guint timeout_id;
GCancellable *cancellable; GCancellable *cancellable;
@@ -527,9 +535,8 @@ port_serial_process_command (MMPortSerial *self,
const gchar *p; const gchar *p;
gsize written; gsize written;
gssize send_len; gssize send_len;
GIOStatus write_status;
if (self->priv->fd < 0 || self->priv->iochannel == NULL) { if (self->priv->iochannel == NULL && self->priv->socket == NULL) {
g_set_error_literal (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, g_set_error_literal (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
"Sending command failed: device is not enabled"); "Sending command failed: device is not enabled");
return FALSE; return FALSE;
@@ -557,40 +564,81 @@ port_serial_process_command (MMPortSerial *self,
p = (gchar *)&ctx->command->data[ctx->idx]; p = (gchar *)&ctx->command->data[ctx->idx];
} }
/* Send N bytes of the command */ /* GIOChannel based setup */
write_status = g_io_channel_write_chars (self->priv->iochannel, p, send_len, &written, error); if (self->priv->iochannel) {
switch (write_status) { GIOStatus write_status;
case G_IO_STATUS_ERROR:
g_prefix_error (error, "Sending command failed: ");
return FALSE;
case G_IO_STATUS_EOF: /* Send N bytes of the command */
/* We shouldn't get EOF when writing */ write_status = g_io_channel_write_chars (self->priv->iochannel, p, send_len, &written, error);
g_assert_not_reached (); switch (write_status) {
break; case G_IO_STATUS_ERROR:
g_prefix_error (error, "Sending command failed: ");
return FALSE;
case G_IO_STATUS_NORMAL: case G_IO_STATUS_EOF:
if (written > 0) { /* We shouldn't get EOF when writing */
ctx->idx += written; g_assert_not_reached ();
break;
case G_IO_STATUS_NORMAL:
if (written > 0) {
ctx->idx += written;
break;
}
/* If written == 0, treat as EAGAIN, so fall down */
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 (self, signals[TIMED_OUT], 0, self->priv->n_consecutive_timeouts);
g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
"Sending command failed: '%s'", strerror (errno));
return FALSE;
}
break; break;
} }
/* If written == 0, treat as EAGAIN, so fall down */
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 (self, signals[TIMED_OUT], 0, self->priv->n_consecutive_timeouts);
g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
"Sending command failed: '%s'", strerror (errno));
return FALSE;
}
break;
} }
/* Socket based setup */
else if (self->priv->socket) {
GError *inner_error = NULL;
/* Send N bytes of the command */
written = g_socket_send (self->priv->socket, p, send_len, NULL, &inner_error);
if (written < 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 (self, signals[TIMED_OUT], 0, self->priv->n_consecutive_timeouts);
g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED,
"Sending command failed: '%s'", strerror (errno));
return FALSE;
}
/* Just keep on, will retry... */
written = 0;
}
ctx->idx += written;
} else
g_assert_not_reached ();
if (ctx->idx >= ctx->command->len) if (ctx->idx >= ctx->command->len)
ctx->done = TRUE; ctx->done = TRUE;
@@ -824,11 +872,9 @@ parse_response (MMPortSerial *self,
} }
static gboolean static gboolean
data_available (GIOChannel *source, common_input_available (MMPortSerial *self,
GIOCondition condition, GIOCondition condition)
gpointer data)
{ {
MMPortSerial *self = MM_PORT_SERIAL (data);
char buf[SERIAL_BUF_SIZE + 1]; char buf[SERIAL_BUF_SIZE + 1];
gsize bytes_read; gsize bytes_read;
GIOStatus status; GIOStatus status;
@@ -859,17 +905,44 @@ data_available (GIOChannel *source,
do { do {
bytes_read = 0; bytes_read = 0;
status = g_io_channel_read_chars (source, buf, SERIAL_BUF_SIZE, &bytes_read, &error);
if (status == G_IO_STATUS_ERROR) { if (self->priv->iochannel) {
if (error) { status = g_io_channel_read_chars (self->priv->iochannel,
mm_warn ("(%s): read error: %s", buf,
SERIAL_BUF_SIZE,
&bytes_read,
&error);
if (status == G_IO_STATUS_ERROR) {
if (error) {
mm_warn ("(%s): read error: %s",
mm_port_get_device (MM_PORT (self)),
error->message);
}
g_clear_error (&error);
}
} else if (self->priv->socket) {
bytes_read = g_socket_receive (self->priv->socket,
buf,
SERIAL_BUF_SIZE,
NULL, /* cancellable */
&error);
if (bytes_read == -1) {
bytes_read = 0;
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
status = G_IO_STATUS_AGAIN;
else
status = G_IO_STATUS_ERROR;
mm_warn ("(%s): receive error: %s",
mm_port_get_device (MM_PORT (self)), mm_port_get_device (MM_PORT (self)),
error->message); error->message);
} g_clear_error (&error);
g_clear_error (&error); } else
status = G_IO_STATUS_NORMAL;
} }
/* If no bytes read, just let g_io_channel wait for more data */
/* If no bytes read, just wait for more data */
if (bytes_read == 0) if (bytes_read == 0)
break; break;
@@ -894,28 +967,64 @@ data_available (GIOChannel *source,
g_clear_error (&error); g_clear_error (&error);
} }
} while ( (bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN) } while ( (bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN)
&& (self->priv->watch_id > 0)); && (self->priv->iochannel_id > 0 || self->priv->socket_source != NULL));
return TRUE; return TRUE;
} }
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 static void
data_watch_enable (MMPortSerial *self, gboolean enable) data_watch_enable (MMPortSerial *self, gboolean enable)
{ {
if (self->priv->watch_id) { if (self->priv->iochannel_id) {
if (enable) if (enable)
g_warn_if_fail (self->priv->watch_id == 0); g_warn_if_fail (self->priv->iochannel_id == 0);
g_source_remove (self->priv->watch_id); g_source_remove (self->priv->iochannel_id);
self->priv->watch_id = 0; 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 (enable) {
g_return_if_fail (self->priv->iochannel != NULL); if (self->priv->iochannel) {
self->priv->watch_id = g_io_add_watch (self->priv->iochannel, self->priv->iochannel_id = g_io_add_watch (self->priv->iochannel,
G_IO_IN | G_IO_ERR | G_IO_HUP, G_IO_IN | G_IO_ERR | G_IO_HUP,
data_available, iochannel_input_available,
self); 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 ();
} }
} }
@@ -924,7 +1033,7 @@ port_connected (MMPortSerial *self, GParamSpec *pspec, gpointer user_data)
{ {
gboolean connected; gboolean connected;
if (self->priv->fd < 0) if (!self->priv->iochannel && !self->priv->socket)
return; return;
/* When the port is connected, drop the serial port lock so PPP can do /* When the port is connected, drop the serial port lock so PPP can do
@@ -933,7 +1042,7 @@ port_connected (MMPortSerial *self, GParamSpec *pspec, gpointer user_data)
*/ */
connected = mm_port_get_connected (MM_PORT (self)); connected = mm_port_get_connected (MM_PORT (self));
if (ioctl (self->priv->fd, (connected ? TIOCNXCL : TIOCEXCL)) < 0) { if (self->priv->fd >= 0 && ioctl (self->priv->fd, (connected ? TIOCNXCL : TIOCEXCL)) < 0) {
mm_warn ("(%s): could not %s serial port lock: (%d) %s", mm_warn ("(%s): could not %s serial port lock: (%d) %s",
mm_port_get_device (MM_PORT (self)), mm_port_get_device (MM_PORT (self)),
connected ? "drop" : "re-acquire", connected ? "drop" : "re-acquire",
@@ -989,26 +1098,29 @@ mm_port_serial_open (MMPortSerial *self, GError **error)
g_get_current_time (&tv_start); g_get_current_time (&tv_start);
/* Only open a new file descriptor if we weren't given one already */ /* Non-socket setup needs the fd open */
if (self->priv->fd < 0) { if (mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_UNIX) {
devfile = g_strdup_printf ("/dev/%s", device); /* Only open a new file descriptor if we weren't given one already */
errno = 0; if (self->priv->fd < 0) {
self->priv->fd = open (devfile, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY); devfile = g_strdup_printf ("/dev/%s", device);
errno_save = errno; errno = 0;
g_free (devfile); self->priv->fd = open (devfile, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
} errno_save = errno;
g_free (devfile);
}
if (self->priv->fd < 0) { if (self->priv->fd < 0) {
/* nozomi isn't ready yet when the port appears, and it'll return /* 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 * ENODEV when open(2) is called on it. Make sure we can handle this
* by returning a special error in that case. * by returning a special error in that case.
*/ */
g_set_error (error, g_set_error (error,
MM_SERIAL_ERROR, MM_SERIAL_ERROR,
(errno == ENODEV) ? MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE : MM_SERIAL_ERROR_OPEN_FAILED, (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)); "Could not open serial device %s: %s", device, strerror (errno_save));
mm_warn ("(%s) could not open serial device (%d)", device, errno_save); mm_warn ("(%s) could not open serial device (%d)", device, errno_save);
return FALSE; return FALSE;
}
} }
/* Serial port specific setup */ /* Serial port specific setup */
@@ -1046,7 +1158,7 @@ mm_port_serial_open (MMPortSerial *self, GError **error)
} }
g_warn_if_fail (MM_PORT_SERIAL_GET_CLASS (self)->config_fd); g_warn_if_fail (MM_PORT_SERIAL_GET_CLASS (self)->config_fd);
if (!MM_PORT_SERIAL_GET_CLASS (self)->config_fd (self, self->priv->fd, error)) { if (self->priv->fd >= 0 && !MM_PORT_SERIAL_GET_CLASS (self)->config_fd (self, self->priv->fd, error)) {
mm_dbg ("(%s) failed to configure serial device", device); mm_dbg ("(%s) failed to configure serial device", device);
goto error; goto error;
} }
@@ -1056,21 +1168,57 @@ mm_port_serial_open (MMPortSerial *self, GError **error)
if (tv_end.tv_sec - tv_start.tv_sec > 7) if (tv_end.tv_sec - tv_start.tv_sec > 7)
mm_warn ("(%s): open blocked by driver for more than 7 seconds!", device); mm_warn ("(%s): open blocked by driver for more than 7 seconds!", device);
/* Create new GIOChannel */ if (mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_UNIX) {
self->priv->iochannel = g_io_channel_unix_new (self->priv->fd); /* 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 */ /* We don't want UTF-8 encoding, we're playing with raw binary data */
g_io_channel_set_encoding (self->priv->iochannel, NULL, NULL); g_io_channel_set_encoding (self->priv->iochannel, NULL, NULL);
/* We don't want to get the channel buffered */ /* We don't want to get the channel buffered */
g_io_channel_set_buffered (self->priv->iochannel, FALSE); g_io_channel_set_buffered (self->priv->iochannel, FALSE);
/* We don't want to get blocked while writing stuff */ /* We don't want to get blocked while writing stuff */
if (!g_io_channel_set_flags (self->priv->iochannel, if (!g_io_channel_set_flags (self->priv->iochannel,
G_IO_FLAG_NONBLOCK, G_IO_FLAG_NONBLOCK,
error)) { error)) {
g_prefix_error (error, "Cannot set non-blocking channel: "); g_prefix_error (error, "Cannot set non-blocking channel: ");
goto error; 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 */ /* Reading watch enable */
@@ -1101,8 +1249,17 @@ error:
self->priv->iochannel = NULL; self->priv->iochannel = NULL;
} }
close (self->priv->fd); if (self->priv->socket) {
self->priv->fd = -1; 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; return FALSE;
} }
@@ -1146,7 +1303,7 @@ mm_port_serial_close (MMPortSerial *self)
mm_port_serial_flash_cancel (self); mm_port_serial_flash_cancel (self);
if (self->priv->fd >= 0) { if (self->priv->iochannel || self->priv->socket) {
GTimeVal tv_start, tv_end; GTimeVal tv_start, tv_end;
struct serial_struct sinfo = { 0 }; struct serial_struct sinfo = { 0 };
@@ -1157,7 +1314,7 @@ mm_port_serial_close (MMPortSerial *self)
g_get_current_time (&tv_start); g_get_current_time (&tv_start);
/* Serial port specific setup */ /* Serial port specific setup */
if (mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY) { 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 /* Paranoid: ensure our closing_wait value is still set so we ignore
* pending data when closing the port. See GNOME bug #630670. * pending data when closing the port. See GNOME bug #630670.
*/ */
@@ -1183,8 +1340,19 @@ mm_port_serial_close (MMPortSerial *self)
self->priv->iochannel = NULL; self->priv->iochannel = NULL;
} }
close (self->priv->fd); /* Close fd, if any */
self->priv->fd = -1; 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); g_get_current_time (&tv_end);
@@ -1402,6 +1570,8 @@ get_speed (MMPortSerial *self, speed_t *speed, GError **error)
{ {
struct termios options; struct termios options;
g_assert (self->priv->fd >= 0);
memset (&options, 0, sizeof (struct termios)); memset (&options, 0, sizeof (struct termios));
if (tcgetattr (self->priv->fd, &options) != 0) { if (tcgetattr (self->priv->fd, &options) != 0) {
g_set_error (error, g_set_error (error,
@@ -1423,6 +1593,8 @@ set_speed (MMPortSerial *self, speed_t speed, GError **error)
int fd, count = 4; int fd, count = 4;
gboolean success = FALSE; gboolean success = FALSE;
g_assert (self->priv->fd >= 0);
fd = self->priv->fd; fd = self->priv->fd;
memset (&options, 0, sizeof (struct termios)); memset (&options, 0, sizeof (struct termios));
@@ -1536,7 +1708,7 @@ flash_do (MMPortSerial *self)
ctx->flash_id = 0; ctx->flash_id = 0;
if (self->priv->flash_ok || mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY) { if (self->priv->flash_ok && mm_port_get_subsys (MM_PORT (self)) == MM_PORT_SUBSYS_TTY) {
if (ctx->current_speed) { if (ctx->current_speed) {
if (!set_speed (ctx->self, ctx->current_speed, &error)) if (!set_speed (ctx->self, ctx->current_speed, &error))
g_assert (error); g_assert (error);
@@ -1595,6 +1767,7 @@ mm_port_serial_flash (MMPortSerial *self,
return; return;
} }
/* Flashing only in TTY */
if (!self->priv->flash_ok || mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_TTY) { if (!self->priv->flash_ok || mm_port_get_subsys (MM_PORT (self)) != MM_PORT_SUBSYS_TTY) {
self->priv->flash_ctx = ctx; self->priv->flash_ctx = ctx;
ctx->flash_id = g_idle_add ((GSourceFunc)flash_do, self); ctx->flash_id = g_idle_add ((GSourceFunc)flash_do, self);

View File

@@ -25,8 +25,9 @@ typedef enum { /*< underscore_name=mm_port_subsys >*/
MM_PORT_SUBSYS_TTY, MM_PORT_SUBSYS_TTY,
MM_PORT_SUBSYS_NET, MM_PORT_SUBSYS_NET,
MM_PORT_SUBSYS_USB, MM_PORT_SUBSYS_USB,
MM_PORT_SUBSYS_UNIX,
MM_PORT_SUBSYS_LAST = MM_PORT_SUBSYS_USB /*< skip >*/ MM_PORT_SUBSYS_LAST = MM_PORT_SUBSYS_UNIX /*< skip >*/
} MMPortSubsys; } MMPortSubsys;
typedef enum { /*< underscore_name=mm_port_type >*/ typedef enum { /*< underscore_name=mm_port_type >*/