ofono/drivers/qmimodem/qmi.c

1682 lines
34 KiB
C

/*
*
* oFono - Open Source Telephony
*
* Copyright (C) 2011-2012 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "qmi.h"
#include "ctl.h"
typedef void (*qmi_message_func_t)(uint16_t message, uint16_t length,
const void *buffer, void *user_data);
struct qmi_device {
int ref_count;
int fd;
GIOChannel *io;
bool close_on_unref;
guint read_watch;
guint write_watch;
GQueue *req_queue;
GQueue *control_queue;
GQueue *service_queue;
unsigned int next_id;
uint8_t next_control_tid;
uint16_t next_service_tid;
qmi_debug_func_t debug_func;
void *debug_data;
uint16_t control_major;
uint16_t control_minor;
char *version_str;
struct qmi_version *version_list;
uint8_t version_count;
GHashTable *service_list;
unsigned int release_users;
};
struct qmi_service {
int ref_count;
struct qmi_device *device;
uint8_t type;
uint16_t major;
uint16_t minor;
uint8_t client_id;
GList *notify_list;
};
struct qmi_param {
void *data;
uint16_t length;
};
struct qmi_result {
uint16_t message;
uint16_t result;
uint16_t error;
const void *data;
uint16_t length;
};
struct qmi_request {
unsigned int id;
uint16_t tid;
void *buf;
size_t len;
qmi_message_func_t callback;
void *user_data;
};
struct qmi_notify {
unsigned int id;
uint16_t message;
qmi_result_func_t callback;
void *user_data;
qmi_destroy_func_t destroy;
};
struct qmi_mux_hdr {
uint8_t frame; /* Always 0x01 */
uint16_t length; /* Packet size without frame byte */
uint8_t flags; /* Either 0x00 or 0x80 */
uint8_t service; /* Service type (0x00 for control) */
uint8_t client; /* Client identifier (0x00 for control) */
} __attribute__ ((packed));
#define QMI_MUX_HDR_SIZE 6
struct qmi_control_hdr {
uint8_t type; /* Bit 1 = response, Bit 2 = indication */
uint8_t transaction; /* Transaction identifier */
} __attribute__ ((packed));
#define QMI_CONTROL_HDR_SIZE 2
struct qmi_service_hdr {
uint8_t type; /* Bit 2 = response, Bit 3 = indication */
uint16_t transaction; /* Transaction identifier */
} __attribute__ ((packed));
#define QMI_SERVICE_HDR_SIZE 3
struct qmi_message_hdr {
uint16_t message; /* Message identifier */
uint16_t length; /* Message size without header */
uint8_t data[0];
} __attribute__ ((packed));
#define QMI_MESSAGE_HDR_SIZE 4
struct qmi_tlv_hdr {
uint8_t type;
uint16_t length;
uint8_t value[0];
} __attribute__ ((packed));
#define QMI_TLV_HDR_SIZE 3
void qmi_free(void *ptr)
{
free(ptr);
}
static struct qmi_request *__request_alloc(uint8_t service,
uint8_t client, uint16_t message,
uint16_t headroom, const void *data,
uint16_t length, qmi_message_func_t func,
void *user_data, void **head)
{
struct qmi_request *req;
struct qmi_mux_hdr *hdr;
struct qmi_message_hdr *msg;
req = g_try_new0(struct qmi_request, 1);
if (!req)
return NULL;
req->len = QMI_MUX_HDR_SIZE + headroom + QMI_MESSAGE_HDR_SIZE + length;
req->buf = g_try_malloc(req->len);
if (!req->buf) {
g_free(req);
return NULL;
}
hdr = req->buf;
hdr->frame = 0x01;
hdr->length = GUINT16_TO_LE(req->len - 1);
hdr->flags = 0x00;
hdr->service = service;
hdr->client = client;
msg = req->buf + QMI_MUX_HDR_SIZE + headroom;
msg->message = GUINT16_TO_LE(message);
msg->length = GUINT16_TO_LE(length);
if (data && length > 0)
memcpy(req->buf + QMI_MUX_HDR_SIZE + headroom +
QMI_MESSAGE_HDR_SIZE, data, length);
req->callback = func;
req->user_data = user_data;
*head = req->buf + QMI_MUX_HDR_SIZE;
return req;
}
static void __request_free(gpointer data, gpointer user_data)
{
struct qmi_request *req = data;
g_free(req->buf);
g_free(req);
}
static gint __request_compare(gconstpointer a, gconstpointer b)
{
const struct qmi_request *req = a;
uint16_t tid = GPOINTER_TO_UINT(b);
return req->tid - tid;
}
static void __notify_free(gpointer data, gpointer user_data)
{
struct qmi_notify *notify = data;
if (notify->destroy)
notify->destroy(notify->user_data);
g_free(notify);
}
static void __hexdump(const char dir, const unsigned char *buf, size_t len,
qmi_debug_func_t function, void *user_data)
{
static const char hexdigits[] = "0123456789abcdef";
char str[68];
size_t i;
if (!function || !len)
return;
str[0] = dir;
for (i = 0; i < len; i++) {
str[((i % 16) * 3) + 1] = ' ';
str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4];
str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf];
str[(i % 16) + 51] = isprint(buf[i]) ? buf[i] : '.';
if ((i + 1) % 16 == 0) {
str[49] = ' ';
str[50] = ' ';
str[67] = '\0';
function(str, user_data);
str[0] = ' ';
}
}
if (i % 16 > 0) {
size_t j;
for (j = (i % 16); j < 16; j++) {
str[(j * 3) + 1] = ' ';
str[(j * 3) + 2] = ' ';
str[(j * 3) + 3] = ' ';
str[j + 51] = ' ';
}
str[49] = ' ';
str[50] = ' ';
str[67] = '\0';
function(str, user_data);
}
}
static const char *__service_type_to_string(uint8_t type)
{
switch (type) {
case QMI_SERVICE_CONTROL:
return "CTL";
case QMI_SERVICE_WDS:
return "WDS";
case QMI_SERVICE_DMS:
return "DMS";
case QMI_SERVICE_NAS:
return "NAS";
case QMI_SERVICE_QOS:
return "QOS";
case QMI_SERVICE_WMS:
return "WMS";
case QMI_SERVICE_PDS:
return "PDS";
case QMI_SERVICE_AUTH:
return "AUTH";
case QMI_SERVICE_AT:
return "AT";
case QMI_SERVICE_VOICE:
return "VOICE";
case QMI_SERVICE_CAT:
return "CAT";
case QMI_SERVICE_UIM:
return "UIM";
case QMI_SERVICE_PBM:
return "PBM";
case QMI_SERVICE_RMTFS:
return "RMTFS";
case QMI_SERVICE_LOC:
return "LOC";
case QMI_SERVICE_SAR:
return "SAR";
case QMI_SERVICE_CSD:
return "CSD";
case QMI_SERVICE_EFS:
return "EFS";
case QMI_SERVICE_TS:
return "TS";
case QMI_SERVICE_TMD:
return "TMS";
case QMI_SERVICE_CAT_OLD:
return "CAT";
case QMI_SERVICE_RMS:
return "RMS";
case QMI_SERVICE_OMA:
return "OMA";
}
return NULL;
}
static void __debug_msg(const char dir, const void *buf, size_t len,
qmi_debug_func_t function, void *user_data)
{
const struct qmi_mux_hdr *hdr;
const struct qmi_message_hdr *msg;
const char *service;
const void *ptr;
uint16_t offset;
char strbuf[72 + 16], *str;
if (!function || !len)
return;
hdr = buf;
str = strbuf;
service = __service_type_to_string(hdr->service);
if (service)
str += sprintf(str, "%c %s", dir, service);
else
str += sprintf(str, "%c %d", dir, hdr->service);
if (hdr->service == QMI_SERVICE_CONTROL) {
const struct qmi_control_hdr *ctl;
const char *type;
ctl = buf + QMI_MUX_HDR_SIZE;
msg = buf + QMI_MUX_HDR_SIZE + QMI_CONTROL_HDR_SIZE;
ptr = buf + QMI_MUX_HDR_SIZE + QMI_CONTROL_HDR_SIZE +
QMI_MESSAGE_HDR_SIZE;
switch (ctl->type) {
case 0x00:
type = "_req";
break;
case 0x01:
type = "_resp";
break;
case 0x02:
type = "_ind";
break;
default:
type = "";
break;
}
str += sprintf(str, "%s msg=%d len=%d", type,
GUINT16_FROM_LE(msg->message),
GUINT16_FROM_LE(msg->length));
str += sprintf(str, " [client=%d,type=%d,tid=%d,len=%d]",
hdr->client, ctl->type,
ctl->transaction,
GUINT16_FROM_LE(hdr->length));
} else {
const struct qmi_service_hdr *srv;
const char *type;
srv = buf + QMI_MUX_HDR_SIZE;
msg = buf + QMI_MUX_HDR_SIZE + QMI_SERVICE_HDR_SIZE;
ptr = buf + QMI_MUX_HDR_SIZE + QMI_SERVICE_HDR_SIZE +
QMI_MESSAGE_HDR_SIZE;
switch (srv->type) {
case 0x00:
type = "_req";
break;
case 0x02:
type = "_resp";
break;
case 0x04:
type = "_ind";
break;
default:
type = "";
break;
}
str += sprintf(str, "%s msg=%d len=%d", type,
GUINT16_FROM_LE(msg->message),
GUINT16_FROM_LE(msg->length));
str += sprintf(str, " [client=%d,type=%d,tid=%d,len=%d]",
hdr->client, srv->type,
GUINT16_FROM_LE(srv->transaction),
GUINT16_FROM_LE(hdr->length));
}
function(strbuf, user_data);
if (!msg->length)
return;
str = strbuf;
str += sprintf(str, " ");
offset = 0;
while (offset + QMI_TLV_HDR_SIZE < GUINT16_FROM_LE(msg->length)) {
const struct qmi_tlv_hdr *tlv = ptr + offset;
uint16_t tlv_length = GUINT16_FROM_LE(tlv->length);
if (tlv->type == 0x02 && tlv_length == QMI_RESULT_CODE_SIZE) {
const struct qmi_result_code *result = ptr + offset +
QMI_TLV_HDR_SIZE;
str += sprintf(str, " {type=%d,error=%d}", tlv->type,
GUINT16_FROM_LE(result->error));
} else {
str += sprintf(str, " {type=%d,len=%d}", tlv->type,
tlv_length);
}
if (str - strbuf > 72) {
function(strbuf, user_data);
str = strbuf;
str += sprintf(str, " ");
}
offset += QMI_TLV_HDR_SIZE + tlv_length;
}
function(strbuf, user_data);
}
static void __debug_device(struct qmi_device *device,
const char *format, ...)
{
char strbuf[72 + 16];
va_list ap;
if (!device->debug_func)
return;
va_start(ap, format);
vsnprintf(strbuf, sizeof(strbuf), format, ap);
va_end(ap);
device->debug_func(strbuf, device->debug_data);
}
static gboolean can_write_data(GIOChannel *channel, GIOCondition cond,
gpointer user_data)
{
struct qmi_device *device = user_data;
struct qmi_mux_hdr *hdr;
struct qmi_request *req;
ssize_t bytes_written;
req = g_queue_pop_head(device->req_queue);
if (!req)
return FALSE;
bytes_written = write(device->fd, req->buf, req->len);
if (bytes_written < 0)
return FALSE;
__hexdump('>', req->buf, bytes_written,
device->debug_func, device->debug_data);
__debug_msg(' ', req->buf, bytes_written,
device->debug_func, device->debug_data);
hdr = req->buf;
if (hdr->service == QMI_SERVICE_CONTROL)
g_queue_push_tail(device->control_queue, req);
else
g_queue_push_tail(device->service_queue, req);
g_free(req->buf);
req->buf = NULL;
if (g_queue_get_length(device->req_queue) > 0)
return TRUE;
return FALSE;
}
static void write_watch_destroy(gpointer user_data)
{
struct qmi_device *device = user_data;
device->write_watch = 0;
}
static void wakeup_writer(struct qmi_device *device)
{
if (device->write_watch > 0)
return;
device->write_watch = g_io_add_watch_full(device->io, G_PRIORITY_HIGH,
G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
can_write_data, device, write_watch_destroy);
}
static void __request_submit(struct qmi_device *device,
struct qmi_request *req, uint16_t transaction)
{
req->id = device->next_id++;
req->tid = transaction;
g_queue_push_tail(device->req_queue, req);
wakeup_writer(device);
}
static void service_notify(gpointer key, gpointer value, gpointer user_data)
{
struct qmi_service *service = value;
struct qmi_result *result = user_data;
GList *list;
for (list = g_list_first(service->notify_list); list;
list = g_list_next(list)) {
struct qmi_notify *notify = list->data;
if (notify->message == result->message)
notify->callback(result, notify->user_data);
}
}
static void handle_indication(struct qmi_device *device,
uint8_t service_type, uint8_t client_id,
uint16_t message, uint16_t length, const void *data)
{
struct qmi_service *service;
struct qmi_result result;
uint16_t hash_id;
if (service_type == QMI_SERVICE_CONTROL)
return;
result.result = 0;
result.error = 0;
result.message = message;
result.data = data;
result.length = length;
if (client_id == 0xff) {
g_hash_table_foreach(device->service_list,
service_notify, &result);
return;
}
hash_id = service_type | (client_id << 8);
service = g_hash_table_lookup(device->service_list,
GUINT_TO_POINTER(hash_id));
if (!service)
return;
service_notify(NULL, service, &result);
}
static void handle_packet(struct qmi_device *device,
const struct qmi_mux_hdr *hdr, const void *buf)
{
struct qmi_request *req;
uint16_t message, length;
const void *data;
if (hdr->service == QMI_SERVICE_CONTROL) {
const struct qmi_control_hdr *control = buf;
const struct qmi_message_hdr *msg;
GList *list;
/* Ignore control messages with client identifier */
if (hdr->client != 0x00)
return;
msg = buf + QMI_CONTROL_HDR_SIZE;
message = GUINT16_FROM_LE(msg->message);
length = GUINT16_FROM_LE(msg->length);
data = buf + QMI_CONTROL_HDR_SIZE + QMI_MESSAGE_HDR_SIZE;
if (control->type == 0x02 && control->transaction == 0x00) {
handle_indication(device, hdr->service, hdr->client,
message, length, data);
return;
}
list = g_queue_find_custom(device->control_queue,
GUINT_TO_POINTER(control->transaction),
__request_compare);
if (!list)
return;
req = list->data;
g_queue_delete_link(device->control_queue, list);
} else {
const struct qmi_service_hdr *service = buf;
const struct qmi_message_hdr *msg;
uint16_t tid;
GList *list;
msg = buf + QMI_SERVICE_HDR_SIZE;
message = GUINT16_FROM_LE(msg->message);
length = GUINT16_FROM_LE(msg->length);
data = buf + QMI_SERVICE_HDR_SIZE + QMI_MESSAGE_HDR_SIZE;
tid = GUINT16_FROM_LE(service->transaction);
if (service->type == 0x04 && tid == 0x0000) {
handle_indication(device, hdr->service, hdr->client,
message, length, data);
return;
}
list = g_queue_find_custom(device->service_queue,
GUINT_TO_POINTER(tid), __request_compare);
if (!list)
return;
req = list->data;
g_queue_delete_link(device->service_queue, list);
}
if (req->callback)
req->callback(message, length, data, req->user_data);
__request_free(req, NULL);
}
static gboolean received_data(GIOChannel *channel, GIOCondition cond,
gpointer user_data)
{
struct qmi_device *device = user_data;
struct qmi_mux_hdr *hdr;
unsigned char buf[2048];
ssize_t bytes_read;
uint16_t offset;
if (cond & G_IO_NVAL)
return FALSE;
bytes_read = read(device->fd, buf, sizeof(buf));
if (bytes_read < 0)
return TRUE;
__hexdump('<', buf, bytes_read,
device->debug_func, device->debug_data);
offset = 0;
while (offset < bytes_read) {
uint16_t len;
/* Check if QMI mux header fits into packet */
if (bytes_read - offset < QMI_MUX_HDR_SIZE)
break;
hdr = (void *) (buf + offset);
/* Check for fixed frame and flags value */
if (hdr->frame != 0x01 || hdr->flags != 0x80)
break;
len = GUINT16_FROM_LE(hdr->length) + 1;
/* Check that packet size matches frame size */
if (bytes_read - offset < len)
break;
__debug_msg(' ', buf + offset, len,
device->debug_func, device->debug_data);
handle_packet(device, hdr, buf + offset + QMI_MUX_HDR_SIZE);
offset += len;
}
return TRUE;
}
static void read_watch_destroy(gpointer user_data)
{
struct qmi_device *device = user_data;
device->read_watch = 0;
}
static void service_destroy(gpointer data)
{
struct qmi_service *service = data;
if (!service->device)
return;
service->device = NULL;
}
struct qmi_device *qmi_device_new(int fd)
{
struct qmi_device *device;
long flags;
device = g_try_new0(struct qmi_device, 1);
if (!device)
return NULL;
__debug_device(device, "device %p new", device);
device->ref_count = 1;
device->fd = fd;
device->close_on_unref = false;
flags = fcntl(device->fd, F_GETFL, NULL);
if (flags < 0) {
g_free(device);
return NULL;
}
if (!(flags & O_NONBLOCK)) {
if (fcntl(device->fd, F_SETFL, flags | O_NONBLOCK) < 0) {
g_free(device);
return NULL;
}
}
device->io = g_io_channel_unix_new(device->fd);
g_io_channel_set_encoding(device->io, NULL, NULL);
g_io_channel_set_buffered(device->io, FALSE);
device->read_watch = g_io_add_watch_full(device->io, G_PRIORITY_DEFAULT,
G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
received_data, device, read_watch_destroy);
g_io_channel_unref(device->io);
device->req_queue = g_queue_new();
device->control_queue = g_queue_new();
device->service_queue = g_queue_new();
device->next_id = 1;
device->next_control_tid = 1;
device->next_service_tid = 1;
device->service_list = g_hash_table_new_full(g_direct_hash,
g_direct_equal, NULL, service_destroy);
return device;
}
struct qmi_device *qmi_device_ref(struct qmi_device *device)
{
if (!device)
return NULL;
__sync_fetch_and_add(&device->ref_count, 1);
return device;
}
void qmi_device_unref(struct qmi_device *device)
{
if (!device)
return;
if (__sync_sub_and_fetch(&device->ref_count, 1))
return;
__debug_device(device, "device %p free", device);
g_queue_foreach(device->control_queue, __request_free, NULL);
g_queue_free(device->control_queue);
g_queue_foreach(device->service_queue, __request_free, NULL);
g_queue_free(device->service_queue);
g_queue_foreach(device->req_queue, __request_free, NULL);
g_queue_free(device->req_queue);
if (device->write_watch > 0)
g_source_remove(device->write_watch);
if (device->read_watch > 0)
g_source_remove(device->read_watch);
if (device->close_on_unref)
close(device->fd);
g_hash_table_destroy(device->service_list);
g_free(device->version_str);
g_free(device->version_list);
g_free(device);
}
void qmi_device_set_debug(struct qmi_device *device,
qmi_debug_func_t func, void *user_data)
{
if (device == NULL)
return;
device->debug_func = func;
device->debug_data = user_data;
}
void qmi_device_set_close_on_unref(struct qmi_device *device, bool do_close)
{
if (!device)
return;
device->close_on_unref = do_close;
}
static const void *tlv_get(const void *data, uint16_t size,
uint8_t type, uint16_t *length)
{
const void *ptr = data;
uint16_t len = size;
while (len > QMI_TLV_HDR_SIZE) {
const struct qmi_tlv_hdr *tlv = ptr;
uint16_t tlv_length = GUINT16_FROM_LE(tlv->length);
if (tlv->type == type) {
if (length)
*length = tlv_length;
return ptr + QMI_TLV_HDR_SIZE;
}
ptr += QMI_TLV_HDR_SIZE + tlv_length;
len -= QMI_TLV_HDR_SIZE + tlv_length;
}
return NULL;
}
struct discover_data {
struct qmi_device *device;
qmi_discover_func_t func;
void *user_data;
qmi_destroy_func_t destroy;
guint timeout;
};
static void discover_callback(uint16_t message, uint16_t length,
const void *buffer, void *user_data)
{
struct discover_data *data = user_data;
struct qmi_device *device = data->device;
const struct qmi_result_code *result_code;
const struct qmi_service_list *service_list;
const void *ptr;
uint16_t len;
struct qmi_version *list;
uint8_t count;
unsigned int i;
g_source_remove(data->timeout);
count = 0;
list = NULL;
result_code = tlv_get(buffer, length, 0x02, &len);
if (!result_code)
goto done;
if (len != QMI_RESULT_CODE_SIZE)
goto done;
service_list = tlv_get(buffer, length, 0x01, &len);
if (!service_list)
goto done;
if (len < QMI_SERVICE_LIST_SIZE)
goto done;
list = g_try_malloc(sizeof(struct qmi_version) * service_list->count);
if (!list)
goto done;
for (i = 0; i < service_list->count; i++) {
uint16_t major =
GUINT16_FROM_LE(service_list->services[i].major);
uint16_t minor =
GUINT16_FROM_LE(service_list->services[i].minor);
uint8_t type = service_list->services[i].type;
if (type == QMI_SERVICE_CONTROL) {
device->control_major = major;
device->control_minor = minor;
continue;
}
list[count].type = type;
list[count].major = major;
list[count].minor = minor;
list[count].name = __service_type_to_string(type);
count++;
}
ptr = tlv_get(buffer, length, 0x10, &len);
if (!ptr)
goto done;
device->version_str = strndup(ptr + 1, *((uint8_t *) ptr));
service_list = ptr + *((uint8_t *) ptr) + 1;
for (i = 0; i < service_list->count; i++) {
if (service_list->services[i].type == QMI_SERVICE_CONTROL)
continue;
}
done:
device->version_list = list;
device->version_count = count;
if (data->func)
data->func(count, list, data->user_data);
if (data->destroy)
data->destroy(data->user_data);
g_free(data);
}
static gboolean discover_reply(gpointer user_data)
{
struct discover_data *data = user_data;
struct qmi_device *device = data->device;
data->timeout = 0;
if (data->func)
data->func(device->version_count,
device->version_list, data->user_data);
if (data->destroy)
data->destroy(data->user_data);
g_free(data);
return FALSE;
}
bool qmi_device_discover(struct qmi_device *device, qmi_discover_func_t func,
void *user_data, qmi_destroy_func_t destroy)
{
struct discover_data *data;
struct qmi_request *req;
struct qmi_control_hdr *hdr;
if (!device)
return false;
__debug_device(device, "device %p discover", device);
data = g_try_new0(struct discover_data, 1);
if (!data)
return false;
data->device = device;
data->func = func;
data->user_data = user_data;
data->destroy = destroy;
if (device->version_list) {
g_timeout_add_seconds(0, discover_reply, data);
return true;
}
req = __request_alloc(QMI_SERVICE_CONTROL, 0x00,
QMI_CTL_GET_VERSION_INFO, QMI_CONTROL_HDR_SIZE,
NULL, 0, discover_callback, data, (void **) &hdr);
if (!req) {
g_free(data);
return false;
}
hdr->type = 0x00;
hdr->transaction = device->next_control_tid++;
__request_submit(device, req, hdr->transaction);
data->timeout = g_timeout_add_seconds(5, discover_reply, data);
return true;
}
static void release_client(struct qmi_device *device,
uint8_t type, uint8_t client_id,
qmi_message_func_t func, void *user_data)
{
unsigned char release_req[] = { 0x01, 0x02, 0x00, type, client_id };
struct qmi_request *req;
struct qmi_control_hdr *hdr;
req = __request_alloc(QMI_SERVICE_CONTROL, 0x00,
QMI_CTL_RELEASE_CLIENT_ID, QMI_CONTROL_HDR_SIZE,
release_req, sizeof(release_req),
func, user_data, (void **) &hdr);
if (!req) {
func(0x0000, 0x0000, NULL, user_data);
return;
}
hdr->type = 0x00;
hdr->transaction = device->next_control_tid++;
__request_submit(device, req, hdr->transaction);
}
struct shutdown_data {
struct qmi_device *device;
qmi_shutdown_func_t func;
void *user_data;
qmi_destroy_func_t destroy;
};
static gboolean shutdown_reply(gpointer user_data)
{
struct shutdown_data *data = user_data;
if (data->func)
data->func(data->user_data);
g_free(data);
return FALSE;
}
static gboolean shutdown_timeout(gpointer user_data)
{
struct shutdown_data *data = user_data;
struct qmi_device *device = data->device;
if (device->release_users > 0)
return TRUE;
return shutdown_reply(data);
}
bool qmi_device_shutdown(struct qmi_device *device, qmi_shutdown_func_t func,
void *user_data, qmi_destroy_func_t destroy)
{
struct shutdown_data *data;
if (!device)
return false;
__debug_device(device, "device %p shutdown", device);
data = g_try_new0(struct shutdown_data, 1);
if (!data)
return false;
data->device = device;
data->func = func;
data->user_data = user_data;
data->destroy = destroy;
if (device->release_users > 0)
g_timeout_add_seconds(0, shutdown_timeout, data);
else
g_timeout_add_seconds(0, shutdown_reply, data);
return true;
}
struct qmi_param *qmi_param_new(void)
{
struct qmi_param *param;
param = g_try_new0(struct qmi_param, 1);
if (!param)
return NULL;
return param;
}
void qmi_param_free(struct qmi_param *param)
{
if (!param)
return;
g_free(param->data);
g_free(param);
}
bool qmi_param_append(struct qmi_param *param, uint8_t type,
uint16_t length, const void *data)
{
struct qmi_tlv_hdr *tlv;
void *ptr;
if (!param || !type)
return false;
if (!length)
return true;
if (!data)
return false;
if (param->data)
ptr = g_try_realloc(param->data,
param->length + QMI_TLV_HDR_SIZE + length);
else
ptr = g_try_malloc(QMI_TLV_HDR_SIZE + length);
if (!ptr)
return false;
tlv = ptr + param->length;
tlv->type = type;
tlv->length = GUINT16_TO_LE(length);
memcpy(tlv->value, data, length);
param->data = ptr;
param->length += QMI_TLV_HDR_SIZE + length;
return true;
}
bool qmi_param_append_uint8(struct qmi_param *param, uint8_t type,
uint8_t value)
{
unsigned char buf[1] = { value };
return qmi_param_append(param, type, sizeof(buf), buf);
}
bool qmi_param_append_uint16(struct qmi_param *param, uint8_t type,
uint16_t value)
{
unsigned char buf[2] = { value & 0xff, (value & 0xff00) >> 8 };
return qmi_param_append(param, type, sizeof(buf), buf);
}
bool qmi_param_append_uint32(struct qmi_param *param, uint8_t type,
uint32_t value)
{
unsigned char buf[4] = { value & 0xff, (value & 0xff00) >> 8,
(value & 0xff0000) >> 16,
(value & 0xff000000) >> 24 };
return qmi_param_append(param, type, sizeof(buf), buf);
}
struct qmi_param *qmi_param_new_uint8(uint8_t type, uint8_t value)
{
struct qmi_param *param;
param = qmi_param_new();
if (!param)
return NULL;
if (!qmi_param_append_uint8(param, type, value)) {
qmi_param_free(param);
return NULL;
}
return param;
}
struct qmi_param *qmi_param_new_uint16(uint8_t type, uint16_t value)
{
struct qmi_param *param;
param = qmi_param_new();
if (!param)
return NULL;
if (!qmi_param_append_uint16(param, type, value)) {
qmi_param_free(param);
return NULL;
}
return param;
}
struct qmi_param *qmi_param_new_uint32(uint8_t type, uint32_t value)
{
struct qmi_param *param;
param = qmi_param_new();
if (!param)
return NULL;
if (!qmi_param_append_uint32(param, type, value)) {
qmi_param_free(param);
return NULL;
}
return param;
}
bool qmi_result_set_error(struct qmi_result *result, uint16_t *error)
{
if (!result) {
if (error)
*error = 0xffff;
return true;
}
if (result->result == 0x0000)
return false;
if (error)
*error = result->error;
return true;
}
const void *qmi_result_get(struct qmi_result *result, uint8_t type,
uint16_t *length)
{
if (!result || !type)
return NULL;
return tlv_get(result->data, result->length, type, length);
}
char *qmi_result_get_string(struct qmi_result *result, uint8_t type)
{
const void *ptr;
uint16_t len;
if (!result || !type)
return NULL;
ptr = tlv_get(result->data, result->length, type, &len);
if (!ptr)
return NULL;
return strndup(ptr, len);
}
bool qmi_result_get_uint8(struct qmi_result *result, uint8_t type,
uint8_t *value)
{
const unsigned char *ptr;
uint16_t len;
if (!result || !type)
return false;
ptr = tlv_get(result->data, result->length, type, &len);
if (!ptr)
return false;
if (value)
*value = *ptr;
return true;
}
bool qmi_result_get_uint16(struct qmi_result *result, uint8_t type,
uint16_t *value)
{
const unsigned char *ptr;
uint16_t len, tmp;
if (!result || !type)
return false;
ptr = tlv_get(result->data, result->length, type, &len);
if (!ptr)
return false;
memcpy(&tmp, ptr, 2);
if (value)
*value = GUINT16_FROM_LE(tmp);
return true;
}
bool qmi_result_get_uint32(struct qmi_result *result, uint8_t type,
uint32_t *value)
{
const unsigned char *ptr;
uint16_t len;
uint32_t tmp;
if (!result || !type)
return false;
ptr = tlv_get(result->data, result->length, type, &len);
if (!ptr)
return false;
memcpy(&tmp, ptr, 4);
if (value)
*value = GUINT32_FROM_LE(tmp);
return true;
}
struct service_create_data {
struct qmi_device *device;
uint8_t type;
uint16_t major;
uint16_t minor;
qmi_create_func_t func;
void *user_data;
qmi_destroy_func_t destroy;
guint timeout;
};
static gboolean service_create_reply(gpointer user_data)
{
struct service_create_data *data = user_data;
data->func(NULL, data->user_data);
if (data->destroy)
data->destroy(data->user_data);
g_free(data);
return FALSE;
}
static void service_create_callback(uint16_t message, uint16_t length,
const void *buffer, void *user_data)
{
struct service_create_data *data = user_data;
struct qmi_device *device = data->device;
struct qmi_service *service = NULL;
const struct qmi_result_code *result_code;
const struct qmi_client_id *client_id;
uint16_t len, hash_id;
g_source_remove(data->timeout);
result_code = tlv_get(buffer, length, 0x02, &len);
if (!result_code)
goto done;
if (len != QMI_RESULT_CODE_SIZE)
goto done;
client_id = tlv_get(buffer, length, 0x01, &len);
if (!client_id)
goto done;
if (len != QMI_CLIENT_ID_SIZE)
goto done;
if (client_id->service != data->type)
goto done;
service = g_try_new0(struct qmi_service, 1);
if (!service)
goto done;
service->ref_count = 1;
service->device = data->device;
service->type = data->type;
service->major = data->major;
service->minor = data->minor;
service->client_id = client_id->client;
__debug_device(device, "service created [client=%d,type=%d]",
service->client_id, service->type);
hash_id = service->type | (service->client_id << 8);
g_hash_table_replace(device->service_list,
GUINT_TO_POINTER(hash_id), service);
done:
data->func(service, data->user_data);
qmi_service_unref(service);
if (data->destroy)
data->destroy(data->user_data);
g_free(data);
}
static void service_create_discover(uint8_t count,
const struct qmi_version *list, void *user_data)
{
struct service_create_data *data = user_data;
struct qmi_device *device = data->device;
struct qmi_request *req;
struct qmi_control_hdr *hdr;
unsigned char client_req[] = { 0x01, 0x01, 0x00, data->type };
unsigned int i;
__debug_device(device, "service create [type=%d]", data->type);
for (i = 0; i < count; i++) {
if (list[i].type == data->type) {
data->major = list[i].major;
data->minor = list[i].minor;
break;
}
}
req = __request_alloc(QMI_SERVICE_CONTROL, 0x00,
QMI_CTL_GET_CLIENT_ID, QMI_CONTROL_HDR_SIZE,
client_req, sizeof(client_req),
service_create_callback, data, (void **) &hdr);
if (!req) {
if (data->timeout > 0)
g_source_remove(data->timeout);
g_timeout_add_seconds(0, service_create_reply, data);
return;
}
hdr->type = 0x00;
hdr->transaction = device->next_control_tid++;
__request_submit(device, req, hdr->transaction);
}
bool qmi_service_create(struct qmi_device *device,
uint8_t type, qmi_create_func_t func,
void *user_data, qmi_destroy_func_t destroy)
{
struct service_create_data *data;
if (!device || !func)
return false;
if (type == QMI_SERVICE_CONTROL)
return false;
data = g_try_new0(struct service_create_data, 1);
if (!data)
return false;
data->device = device;
data->type = type;
data->func = func;
data->user_data = user_data;
if (device->version_list) {
service_create_discover(device->version_count,
device->version_list, data);
goto done;
}
if (qmi_device_discover(device, service_create_discover, data, NULL))
goto done;
g_free(data);
return false;
done:
data->timeout = g_timeout_add_seconds(8, service_create_reply, data);
return true;
}
static void service_release_callback(uint16_t message, uint16_t length,
const void *buffer, void *user_data)
{
struct qmi_service *service = user_data;
if (service->device)
service->device->release_users--;
g_free(service);
}
struct qmi_service *qmi_service_ref(struct qmi_service *service)
{
if (!service)
return NULL;
__sync_fetch_and_add(&service->ref_count, 1);
return service;
}
void qmi_service_unref(struct qmi_service *service)
{
uint16_t hash_id;
if (!service)
return;
if (__sync_sub_and_fetch(&service->ref_count, 1))
return;
if (!service->device) {
g_free(service);
return;
}
hash_id = service->type | (service->client_id << 8);
g_hash_table_steal(service->device->service_list,
GUINT_TO_POINTER(hash_id));
service->device->release_users++;
release_client(service->device, service->type, service->client_id,
service_release_callback, service);
}
const char *qmi_service_get_identifier(struct qmi_service *service)
{
if (!service)
return NULL;
return __service_type_to_string(service->type);
}
bool qmi_service_get_version(struct qmi_service *service,
uint16_t *major, uint16_t *minor)
{
if (!service)
return false;
if (major)
*major = service->major;
if (minor)
*minor = service->minor;
return true;
}
struct service_send_data {
struct qmi_service *service;
struct qmi_param *param;
qmi_result_func_t func;
void *user_data;
qmi_destroy_func_t destroy;
};
static void service_send_callback(uint16_t message, uint16_t length,
const void *buffer, void *user_data)
{
struct service_send_data *data = user_data;
const struct qmi_result_code *result_code;
uint16_t len;
struct qmi_result result;
result.message = message;
result.data = buffer;
result.length = length;
result_code = tlv_get(buffer, length, 0x02, &len);
if (!result_code)
goto done;
if (len != QMI_RESULT_CODE_SIZE)
goto done;
result.result = GUINT16_FROM_LE(result_code->result);
result.error = GUINT16_FROM_LE(result_code->error);
done:
if (data->func)
data->func(&result, data->user_data);
if (data->destroy)
data->destroy(data->user_data);
qmi_param_free(data->param);
g_free(data);
}
unsigned int qmi_service_send(struct qmi_service *service,
uint16_t message, struct qmi_param *param,
qmi_result_func_t func,
void *user_data, qmi_destroy_func_t destroy)
{
struct qmi_device *device;
struct service_send_data *data;
struct qmi_request *req;
struct qmi_service_hdr *hdr;
if (!service)
return 0;
if (!service->client_id)
return 0;
device = service->device;
if (!device)
return 0;
data = g_try_new0(struct service_send_data, 1);
if (!data)
return 0;
data->service = service;
data->param = param;
data->func = func;
data->user_data = user_data;
data->destroy = destroy;
req = __request_alloc(service->type, service->client_id,
message, QMI_SERVICE_HDR_SIZE,
data->param ? data->param->data : NULL,
data->param ? data->param->length : 0,
service_send_callback, data, (void **) &hdr);
if (!req) {
g_free(data);
return 0;
}
hdr->type = 0x00;
hdr->transaction = device->next_service_tid++;
__request_submit(device, req, hdr->transaction);
return req->id;
}
unsigned int qmi_service_register(struct qmi_service *service,
uint16_t message, qmi_result_func_t func,
void *user_data, qmi_destroy_func_t destroy)
{
struct qmi_notify *notify;
if (!service || !func)
return 0;
notify = g_try_new0(struct qmi_notify, 1);
if (!notify)
return 0;
notify->id = service->device->next_id++;
notify->message = message;
notify->callback = func;
notify->user_data = user_data;
notify->destroy = destroy;
service->notify_list = g_list_append(service->notify_list, notify);
return notify->id;
}
bool qmi_service_unregister_all(struct qmi_service *service)
{
if (!service)
return false;
g_list_foreach(service->notify_list, __notify_free, NULL);
g_list_free(service->notify_list);
service->notify_list = NULL;
return true;
}