diff options
author | Dan Williams <dcbw@redhat.com> | 2010-02-20 14:55:10 -0800 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2010-02-20 14:55:10 -0800 |
commit | a8c7bba19ea486bd21f4fd1f2050bf899478fdaa (patch) | |
tree | 6ca97c3311be04d46b5bf73227cf2c96bff4e4a7 /src/mm-at-serial-port.c | |
parent | a431455059414b4a1cad854776c7fc0572e8c7fc (diff) |
serial: refactor MMSerialPort into a base class and an AT-capable serial port
For QCDM devices we want most of what MMSerialPort does, but not
the AT command handling stuff since the commands and responses
aren't AT commands nor are they even strings. So convert everything
that MMSerialPort does into a GByteArray, and let MMAtSerialPort
handle the conversion to strings when necessary.
Diffstat (limited to 'src/mm-at-serial-port.c')
-rw-r--r-- | src/mm-at-serial-port.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/src/mm-at-serial-port.c b/src/mm-at-serial-port.c new file mode 100644 index 00000000..fd4ff6e4 --- /dev/null +++ b/src/mm-at-serial-port.c @@ -0,0 +1,323 @@ +/* -*- 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 <unistd.h> +#include <string.h> + +#include "mm-at-serial-port.h" +#include "mm-errors.h" +#include "mm-options.h" + +G_DEFINE_TYPE (MMAtSerialPort, mm_at_serial_port, MM_TYPE_SERIAL_PORT) + +#define MM_AT_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPortPrivate)) + +typedef struct { + /* Response parser data */ + MMAtSerialResponseParserFn response_parser_fn; + gpointer response_parser_user_data; + GDestroyNotify response_parser_notify; + GSList *unsolicited_msg_handlers; +} MMAtSerialPortPrivate; + + +/*****************************************************************************/ + +void +mm_at_serial_port_set_response_parser (MMAtSerialPort *self, + MMAtSerialResponseParserFn fn, + gpointer user_data, + GDestroyNotify notify) +{ + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + + g_return_if_fail (MM_IS_AT_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 +parse_response (MMSerialPort *port, GByteArray *response, GError **error) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + gboolean found; + GString *string; + + g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE); + + /* Construct the string that AT-parsing functions expect */ + string = g_string_sized_new (response->len + 1); + g_string_append_len (string, (const char *) response->data, response->len); + + /* Parse it */ + found = priv->response_parser_fn (priv->response_parser_user_data, string, error); + + /* And copy it back into the response array after the parser has removed + * matches and cleaned it up. + */ + g_byte_array_remove_range (response, 0, response->len); + g_byte_array_append (response, (const guint8 *) string->str, string->len); + g_string_free (string, TRUE); + return found; +} + +static void +handle_response (MMSerialPort *port, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialResponseFn response_callback = (MMAtSerialResponseFn) callback; + GString *string; + + /* Convert to a string and call the callback */ + string = g_string_sized_new (response->len + 1); + g_string_append_len (string, (const char *) response->data, response->len); + response_callback (self, string, error, callback_data); + g_string_free (string, TRUE); +} + +/*****************************************************************************/ + +typedef struct { + GRegex *regex; + MMAtSerialUnsolicitedMsgFn callback; + gpointer user_data; + GDestroyNotify notify; +} MMAtUnsolicitedMsgHandler; + +void +mm_at_serial_port_add_unsolicited_msg_handler (MMAtSerialPort *self, + GRegex *regex, + MMAtSerialUnsolicitedMsgFn callback, + gpointer user_data, + GDestroyNotify notify) +{ + MMAtUnsolicitedMsgHandler *handler; + MMAtSerialPortPrivate *priv; + + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (regex != NULL); + + handler = g_slice_new (MMAtUnsolicitedMsgHandler); + handler->regex = g_regex_ref (regex); + handler->callback = callback; + handler->user_data = user_data; + handler->notify = notify; + + priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + priv->unsolicited_msg_handlers = g_slist_append (priv->unsolicited_msg_handlers, handler); +} + +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 (MMSerialPort *port, GByteArray *response) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + GSList *iter; + + for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) { + MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) iter->data; + GMatchInfo *match_info; + gboolean matches; + + matches = g_regex_match_full (handler->regex, + (const char *) response->data, + 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, + (const char *) response->data, + response->len, + 0, 0, + remove_eval_cb, &result_len, NULL); + + g_byte_array_remove_range (response, 0, response->len); + g_byte_array_append (response, (const guint8 *) str, result_len); + g_free (str); + } + } +} + +/*****************************************************************************/ + +static GByteArray * +at_command_to_byte_array (const char *command) +{ + GByteArray *buf; + int cmdlen; + + g_return_val_if_fail (command != NULL, NULL); + + cmdlen = strlen (command); + buf = g_byte_array_sized_new (cmdlen + 3); + + /* Make sure there's an AT in the front */ + if (!g_str_has_prefix (command, "AT")) + g_byte_array_append (buf, (const guint8 *) "AT", 2); + g_byte_array_append (buf, (const guint8 *) command, cmdlen); + + /* Make sure there's a trailing carriage return */ + if (command[cmdlen] != '\r') + g_byte_array_append (buf, (const guint8 *) "\r", 1); + + return buf; +} + +void +mm_at_serial_port_queue_command (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data) +{ + GByteArray *buf; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + buf = at_command_to_byte_array (command); + g_return_if_fail (buf != NULL); + + mm_serial_port_queue_command (MM_SERIAL_PORT (self), + buf, + TRUE, + timeout_seconds, + G_CALLBACK (callback), + user_data); +} + +void +mm_at_serial_port_queue_command_cached (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data) +{ + GByteArray *buf; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + buf = at_command_to_byte_array (command); + g_return_if_fail (buf != NULL); + + mm_serial_port_queue_command_cached (MM_SERIAL_PORT (self), + buf, + TRUE, + timeout_seconds, + G_CALLBACK (callback), + user_data); +} + +/*****************************************************************************/ + +MMAtSerialPort * +mm_at_serial_port_new (const char *name, MMPortType ptype) +{ + return MM_AT_SERIAL_PORT (g_object_new (MM_TYPE_AT_SERIAL_PORT, + MM_PORT_DEVICE, name, + MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY, + MM_PORT_TYPE, ptype, + NULL)); +} + +static void +mm_at_serial_port_init (MMAtSerialPort *self) +{ +} + +static void +finalize (GObject *object) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (object); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + + while (priv->unsolicited_msg_handlers) { + MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) priv->unsolicited_msg_handlers->data; + + if (handler->notify) + handler->notify (handler->user_data); + + g_regex_unref (handler->regex); + g_slice_free (MMAtUnsolicitedMsgHandler, 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_at_serial_port_parent_class)->finalize (object); +} + +static void +mm_at_serial_port_class_init (MMAtSerialPortClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMSerialPortClass *port_class = MM_SERIAL_PORT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMAtSerialPortPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + + port_class->parse_unsolicited = parse_unsolicited; + port_class->parse_response = parse_response; + port_class->handle_response = handle_response; +} |