blob: 4d26e39857c68f6eff21f3db47fec4057bb3e512 [file] [log] [blame]
/* -*- 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ModemManager.h>
#include <mm-errors-types.h>
#include "mm-port-serial-qcdm.h"
#include "libqcdm/src/com.h"
#include "libqcdm/src/utils.h"
#include "libqcdm/src/errors.h"
#include "libqcdm/src/dm-commands.h"
#include "mm-log.h"
G_DEFINE_TYPE (MMPortSerialQcdm, mm_port_serial_qcdm, MM_TYPE_PORT_SERIAL)
struct _MMPortSerialQcdmPrivate {
GSList *unsolicited_msg_handlers;
};
/*****************************************************************************/
static gboolean
find_qcdm_start (GByteArray *response, gsize *start)
{
int i, last = -1;
/* Look for 3 bytes and a QCDM frame marker, ie enough data for a valid
* frame. There will usually be three cases here; (1) a QCDM frame
* starting with data and terminated by 0x7E, and (2) a QCDM frame starting
* with 0x7E and ending with 0x7E, and (3) a non-QCDM frame that still
* uses HDLC framing (like Sierra CnS) that starts and ends with 0x7E.
*/
for (i = 0; i < response->len; i++) {
if (response->data[i] == 0x7E) {
if (i > last + 3) {
/* Got a full QCDM frame; 3 non-0x7E bytes and a terminator */
if (start)
*start = last + 1;
return TRUE;
}
/* Save position of the last QCDM frame marker */
last = i;
}
}
return FALSE;
}
static MMPortSerialResponseType
parse_qcdm (GByteArray *response,
gboolean want_log,
GByteArray **parsed_response,
GError **error)
{
gsize start = 0;
gsize used = 0;
gsize unescaped_len = 0;
guint8 *unescaped_buffer;
qcdmbool more = FALSE;
/* Get the offset into the buffer of where the QCDM frame starts */
if (!find_qcdm_start (response, &start)) {
/* Discard the unparsable data right away, we do need a QCDM
* start, and anything that comes before it is unknown data
* that we'll never use. */
return MM_PORT_SERIAL_RESPONSE_NONE;
}
/* If there is anything before the start marker, remove it */
g_byte_array_remove_range (response, 0, start);
if (response->len == 0)
return MM_PORT_SERIAL_RESPONSE_NONE;
/* Try to decapsulate the response into a buffer */
unescaped_buffer = g_malloc (1024);
if (!dm_decapsulate_buffer ((const char *)(response->data),
response->len,
(char *)unescaped_buffer,
1024,
&unescaped_len,
&used,
&more)) {
/* Report an error right away. Not being able to decapsulate a QCDM
* packet once we got message start marker likely means that this
* data that we got is not a QCDM message. */
g_set_error (error,
MM_SERIAL_ERROR,
MM_SERIAL_ERROR_PARSE_FAILED,
"Failed to unescape QCDM packet");
g_free (unescaped_buffer);
return MM_PORT_SERIAL_RESPONSE_ERROR;
}
if (more) {
/* Need more data, we leave the original byte array untouched so that
* we can retry later when more data arrives. */
g_free (unescaped_buffer);
return MM_PORT_SERIAL_RESPONSE_NONE;
}
if (want_log && unescaped_buffer[0] != DIAG_CMD_LOG) {
/* If we only want log items and this isn't one, don't remove this
* DM packet from the buffer.
*/
g_free (unescaped_buffer);
return MM_PORT_SERIAL_RESPONSE_NONE;
}
/* Successfully decapsulated the DM command. We'll build a new byte array
* with the response, and leave the input buffer cleaned up. */
g_assert (unescaped_len <= 1024);
unescaped_buffer = g_realloc (unescaped_buffer, unescaped_len);
*parsed_response = g_byte_array_new_take (unescaped_buffer, unescaped_len);
/* Remove the data we used from the input buffer, leaving out any
* additional data that may already been received (e.g. from the following
* message). */
g_byte_array_remove_range (response, 0, used);
return MM_PORT_SERIAL_RESPONSE_BUFFER;
}
static MMPortSerialResponseType
parse_response (MMPortSerial *port,
GByteArray *response,
GByteArray **parsed_response,
GError **error)
{
return parse_qcdm (response, FALSE, parsed_response, error);
}
/*****************************************************************************/
GByteArray *
mm_port_serial_qcdm_command_finish (MMPortSerialQcdm *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
serial_command_ready (MMPortSerial *port,
GAsyncResult *res,
GTask *task)
{
GByteArray *response;
GError *error = NULL;
response = mm_port_serial_command_finish (port, res, &error);
if (!response)
g_task_return_error (task, error);
else
g_task_return_pointer (task, response, (GDestroyNotify)g_byte_array_unref);
g_object_unref (task);
}
void
mm_port_serial_qcdm_command (MMPortSerialQcdm *self,
GByteArray *command,
guint32 timeout_seconds,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_return_if_fail (MM_IS_PORT_SERIAL_QCDM (self));
g_return_if_fail (command != NULL);
task = g_task_new (self, cancellable, callback, user_data);
/* 'command' is expected to be already CRC-ed and escaped */
mm_port_serial_command (MM_PORT_SERIAL (self),
command,
timeout_seconds,
FALSE, /* never cached */
cancellable,
(GAsyncReadyCallback)serial_command_ready,
task);
}
static void
debug_log (MMPortSerial *port, const char *prefix, const char *buf, gsize len)
{
static GString *debug = NULL;
const char *s = buf;
if (!debug)
debug = g_string_sized_new (512);
g_string_append (debug, prefix);
while (len--)
g_string_append_printf (debug, " %02x", (guint8) (*s++ & 0xFF));
mm_dbg ("(%s): %s", mm_port_get_device (MM_PORT (port)), debug->str);
g_string_truncate (debug, 0);
}
/*****************************************************************************/
typedef struct {
guint log_code;
MMPortSerialQcdmUnsolicitedMsgFn callback;
gboolean enable;
gpointer user_data;
GDestroyNotify notify;
} MMQcdmUnsolicitedMsgHandler;
static gint
unsolicited_msg_handler_cmp (MMQcdmUnsolicitedMsgHandler *handler,
gpointer log_code)
{
return handler->log_code - GPOINTER_TO_UINT (log_code);
}
void
mm_port_serial_qcdm_add_unsolicited_msg_handler (MMPortSerialQcdm *self,
guint log_code,
MMPortSerialQcdmUnsolicitedMsgFn callback,
gpointer user_data,
GDestroyNotify notify)
{
GSList *existing;
MMQcdmUnsolicitedMsgHandler *handler;
g_return_if_fail (MM_IS_PORT_SERIAL_QCDM (self));
g_return_if_fail (log_code > 0 && log_code <= G_MAXUINT16);
existing = g_slist_find_custom (self->priv->unsolicited_msg_handlers,
GUINT_TO_POINTER (log_code),
(GCompareFunc)unsolicited_msg_handler_cmp);
if (existing) {
handler = existing->data;
/* We OVERWRITE any existing one, so if any context data existing, free it */
if (handler->notify)
handler->notify (handler->user_data);
} else {
handler = g_slice_new (MMQcdmUnsolicitedMsgHandler);
self->priv->unsolicited_msg_handlers = g_slist_append (self->priv->unsolicited_msg_handlers, handler);
handler->log_code = log_code;
}
handler->callback = callback;
handler->enable = TRUE;
handler->user_data = user_data;
handler->notify = notify;
}
void
mm_port_serial_qcdm_enable_unsolicited_msg_handler (MMPortSerialQcdm *self,
guint log_code,
gboolean enable)
{
GSList *existing;
MMQcdmUnsolicitedMsgHandler *handler;
g_return_if_fail (MM_IS_PORT_SERIAL_QCDM (self));
g_return_if_fail (log_code > 0 && log_code <= G_MAXUINT16);
existing = g_slist_find_custom (self->priv->unsolicited_msg_handlers,
GUINT_TO_POINTER (log_code),
(GCompareFunc)unsolicited_msg_handler_cmp);
if (existing) {
handler = existing->data;
handler->enable = enable;
}
}
static void
parse_unsolicited (MMPortSerial *port, GByteArray *response)
{
MMPortSerialQcdm *self = MM_PORT_SERIAL_QCDM (port);
GByteArray *log_buffer = NULL;
GSList *iter;
if (parse_qcdm (response,
TRUE,
&log_buffer,
NULL) != MM_PORT_SERIAL_RESPONSE_BUFFER) {
return;
}
/* These should be guaranteed by parse_qcdm() */
g_return_if_fail (log_buffer);
g_return_if_fail (log_buffer->len > 0);
g_return_if_fail (log_buffer->data[0] == DIAG_CMD_LOG);
if (log_buffer->len < sizeof (DMCmdLog))
return;
for (iter = self->priv->unsolicited_msg_handlers; iter; iter = iter->next) {
MMQcdmUnsolicitedMsgHandler *handler = (MMQcdmUnsolicitedMsgHandler *) iter->data;
DMCmdLog *log_cmd = (DMCmdLog *) log_buffer->data;
if (!handler->enable)
continue;
if (handler->log_code != le16toh (log_cmd->log_code))
continue;
if (handler->callback)
handler->callback (self, log_buffer, handler->user_data);
}
}
/*****************************************************************************/
static gboolean
config_fd (MMPortSerial *port, int fd, GError **error)
{
int err;
err = qcdm_port_setup (fd);
if (err != QCDM_SUCCESS) {
g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED,
"Failed to open QCDM port: %d", err);
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
MMPortSerialQcdm *
mm_port_serial_qcdm_new (const char *name)
{
return MM_PORT_SERIAL_QCDM (g_object_new (MM_TYPE_PORT_SERIAL_QCDM,
MM_PORT_DEVICE, name,
MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY,
MM_PORT_TYPE, MM_PORT_TYPE_QCDM,
MM_PORT_SERIAL_SEND_DELAY, (guint64) 0,
NULL));
}
MMPortSerialQcdm *
mm_port_serial_qcdm_new_fd (int fd)
{
MMPortSerialQcdm *port;
char *name;
name = g_strdup_printf ("port%d", fd);
port = MM_PORT_SERIAL_QCDM (g_object_new (MM_TYPE_PORT_SERIAL_QCDM,
MM_PORT_DEVICE, name,
MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY,
MM_PORT_TYPE, MM_PORT_TYPE_QCDM,
MM_PORT_SERIAL_FD, fd,
MM_PORT_SERIAL_SEND_DELAY, (guint64) 0,
NULL));
g_free (name);
return port;
}
static void
mm_port_serial_qcdm_init (MMPortSerialQcdm *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_PORT_SERIAL_QCDM, MMPortSerialQcdmPrivate);
}
static void
finalize (GObject *object)
{
MMPortSerialQcdm *self = MM_PORT_SERIAL_QCDM (object);
while (self->priv->unsolicited_msg_handlers) {
MMQcdmUnsolicitedMsgHandler *handler = (MMQcdmUnsolicitedMsgHandler *) self->priv->unsolicited_msg_handlers->data;
if (handler->notify)
handler->notify (handler->user_data);
g_slice_free (MMQcdmUnsolicitedMsgHandler, handler);
self->priv->unsolicited_msg_handlers = g_slist_delete_link (self->priv->unsolicited_msg_handlers,
self->priv->unsolicited_msg_handlers);
}
G_OBJECT_CLASS (mm_port_serial_qcdm_parent_class)->finalize (object);
}
static void
mm_port_serial_qcdm_class_init (MMPortSerialQcdmClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MMPortSerialClass *port_class = MM_PORT_SERIAL_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMPortSerialQcdmPrivate));
/* Virtual methods */
object_class->finalize = finalize;
port_class->parse_unsolicited = parse_unsolicited;
port_class->parse_response = parse_response;
port_class->config_fd = config_fd;
port_class->debug_log = debug_log;
}