mirror of git://git.sysmocom.de/ofono
1289 lines
26 KiB
C
1289 lines
26 KiB
C
/*
|
|
*
|
|
* RIL library with GLib integration
|
|
*
|
|
* Copyright (C) 2008-2011 Intel Corporation. All rights reserved.
|
|
* Copyright (C) 2012-2013 Canonical Ltd.
|
|
* Copyright (C) 2013 Jolla Ltd.
|
|
*
|
|
* 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
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <netinet/in.h>
|
|
#include <unistd.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <ofono/log.h>
|
|
#include "ringbuffer.h"
|
|
#include "gril.h"
|
|
#include "grilutil.h"
|
|
|
|
#define RIL_TRACE(ril, fmt, arg...) do { \
|
|
if (ril->trace == TRUE) \
|
|
ofono_debug(fmt, ## arg); \
|
|
} while (0)
|
|
|
|
struct ril_request {
|
|
gchar *data;
|
|
guint data_len;
|
|
gint req;
|
|
gint id;
|
|
guint gid;
|
|
GRilResponseFunc callback;
|
|
gpointer user_data;
|
|
GDestroyNotify notify;
|
|
};
|
|
|
|
struct ril_notify_node {
|
|
guint id;
|
|
guint gid;
|
|
GRilNotifyFunc callback;
|
|
gpointer user_data;
|
|
gboolean destroyed;
|
|
};
|
|
|
|
typedef gboolean (*node_remove_func)(struct ril_notify_node *node,
|
|
gpointer user_data);
|
|
|
|
struct ril_notify {
|
|
GSList *nodes;
|
|
};
|
|
|
|
struct ril_s {
|
|
gint ref_count; /* Ref count */
|
|
gint next_cmd_id; /* Next command id */
|
|
guint next_notify_id; /* Next notify id */
|
|
guint next_gid; /* Next group id */
|
|
GRilIO *io; /* GRil IO */
|
|
GQueue *command_queue; /* Command queue */
|
|
GQueue *out_queue; /* Commands sent/been sent */
|
|
guint req_bytes_written; /* bytes written from req */
|
|
GHashTable *notify_list; /* List of notification reg */
|
|
GRilDisconnectFunc user_disconnect; /* user disconnect func */
|
|
gpointer user_disconnect_data; /* user disconnect data */
|
|
guint read_so_far; /* Number of bytes processed */
|
|
gboolean suspended; /* Are we suspended? */
|
|
gboolean debug;
|
|
gboolean trace;
|
|
gint timeout_source;
|
|
gboolean destroyed; /* Re-entrancy guard */
|
|
gboolean in_read_handler; /* Re-entrancy guard */
|
|
gboolean in_notify;
|
|
enum ofono_ril_vendor vendor;
|
|
int slot;
|
|
GRilMsgIdToStrFunc req_to_string;
|
|
GRilMsgIdToStrFunc unsol_to_string;
|
|
};
|
|
|
|
struct _GRil {
|
|
gint ref_count;
|
|
struct ril_s *parent;
|
|
guint group;
|
|
};
|
|
|
|
struct req_hdr {
|
|
/* Warning: length is stored in network order */
|
|
uint32_t length;
|
|
uint32_t reqid;
|
|
uint32_t serial;
|
|
};
|
|
|
|
#define RIL_PRINT_BUF_SIZE 8096
|
|
char print_buf[RIL_PRINT_BUF_SIZE] __attribute__((used));
|
|
|
|
static void ril_wakeup_writer(struct ril_s *ril);
|
|
|
|
static const char *request_id_to_string(struct ril_s *ril, int req)
|
|
{
|
|
const char *str = NULL;
|
|
|
|
if (ril->req_to_string)
|
|
str = ril->req_to_string(req);
|
|
|
|
if (str == NULL)
|
|
str = ril_request_id_to_string(req);
|
|
|
|
return str;
|
|
}
|
|
|
|
static const char *unsol_request_to_string(struct ril_s *ril, int req)
|
|
{
|
|
const char *str = NULL;
|
|
|
|
if (ril->unsol_to_string)
|
|
str = ril->unsol_to_string(req);
|
|
|
|
if (str == NULL)
|
|
str = ril_unsol_request_to_string(req);
|
|
|
|
return str;
|
|
}
|
|
|
|
static void ril_notify_node_destroy(gpointer data, gpointer user_data)
|
|
{
|
|
struct ril_notify_node *node = data;
|
|
g_free(node);
|
|
}
|
|
|
|
static void ril_notify_destroy(gpointer user_data)
|
|
{
|
|
struct ril_notify *notify = user_data;
|
|
|
|
g_slist_foreach(notify->nodes, ril_notify_node_destroy, NULL);
|
|
g_slist_free(notify->nodes);
|
|
g_free(notify);
|
|
}
|
|
|
|
static gint ril_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct ril_notify_node *node = a;
|
|
guint id = GPOINTER_TO_UINT(b);
|
|
|
|
if (node->id < id)
|
|
return -1;
|
|
|
|
if (node->id > id)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean ril_unregister_all(struct ril_s *ril,
|
|
gboolean mark_only,
|
|
node_remove_func func,
|
|
gpointer userdata)
|
|
{
|
|
GHashTableIter iter;
|
|
struct ril_notify *notify;
|
|
struct ril_notify_node *node;
|
|
gpointer key, value;
|
|
GSList *p;
|
|
GSList *c;
|
|
GSList *t;
|
|
|
|
if (ril->notify_list == NULL)
|
|
return FALSE;
|
|
|
|
g_hash_table_iter_init(&iter, ril->notify_list);
|
|
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
notify = value;
|
|
|
|
p = NULL;
|
|
c = notify->nodes;
|
|
|
|
while (c) {
|
|
node = c->data;
|
|
|
|
if (func(node, userdata) != TRUE) {
|
|
p = c;
|
|
c = c->next;
|
|
continue;
|
|
}
|
|
|
|
if (mark_only) {
|
|
node->destroyed = TRUE;
|
|
p = c;
|
|
c = c->next;
|
|
continue;
|
|
}
|
|
|
|
if (p)
|
|
p->next = c->next;
|
|
else
|
|
notify->nodes = c->next;
|
|
|
|
ril_notify_node_destroy(node, NULL);
|
|
|
|
t = c;
|
|
c = c->next;
|
|
g_slist_free_1(t);
|
|
}
|
|
|
|
if (notify->nodes == NULL)
|
|
g_hash_table_iter_remove(&iter);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* This function creates a RIL request. For a good reference on
|
|
* the layout of RIL requests, responses, and unsolicited requests
|
|
* see:
|
|
*
|
|
* https://wiki.mozilla.org/B2G/RIL
|
|
*/
|
|
static struct ril_request *ril_request_create(struct ril_s *ril,
|
|
guint gid,
|
|
const gint req,
|
|
const gint id,
|
|
struct parcel *rilp,
|
|
GRilResponseFunc func,
|
|
gpointer user_data,
|
|
GDestroyNotify notify,
|
|
gboolean wakeup)
|
|
{
|
|
struct ril_request *r;
|
|
struct req_hdr header;
|
|
guint data_len = 0;
|
|
|
|
if (rilp != NULL)
|
|
data_len = rilp->size;
|
|
|
|
r = g_try_new0(struct ril_request, 1);
|
|
if (r == NULL) {
|
|
ofono_error("%s Out of memory", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
/* Full request size: header size plus buffer length */
|
|
r->data_len = data_len + sizeof(header);
|
|
|
|
r->data = g_try_new(char, r->data_len);
|
|
if (r->data == NULL) {
|
|
ofono_error("ril_request: can't allocate new request.");
|
|
g_free(r);
|
|
return NULL;
|
|
}
|
|
|
|
/* Length does not include the length field. Network order. */
|
|
header.length = htonl(r->data_len - sizeof(header.length));
|
|
header.reqid = req;
|
|
header.serial = id;
|
|
|
|
/* copy header */
|
|
memcpy(r->data, &header, sizeof(header));
|
|
/* copy request data */
|
|
if (data_len)
|
|
memcpy(r->data + sizeof(header), rilp->data, data_len);
|
|
|
|
r->req = req;
|
|
r->gid = gid;
|
|
r->id = id;
|
|
r->callback = func;
|
|
r->user_data = user_data;
|
|
r->notify = notify;
|
|
|
|
return r;
|
|
}
|
|
|
|
static void ril_request_destroy(struct ril_request *req)
|
|
{
|
|
if (req->notify)
|
|
req->notify(req->user_data);
|
|
|
|
g_free(req->data);
|
|
g_free(req);
|
|
}
|
|
|
|
static void ril_cleanup(struct ril_s *p)
|
|
{
|
|
/* Cleanup pending commands */
|
|
|
|
if (p->command_queue) {
|
|
g_queue_free(p->command_queue);
|
|
p->command_queue = NULL;
|
|
}
|
|
|
|
if (p->out_queue) {
|
|
g_queue_free(p->out_queue);
|
|
p->out_queue = NULL;
|
|
}
|
|
|
|
/* Cleanup registered notifications */
|
|
if (p->notify_list) {
|
|
g_hash_table_destroy(p->notify_list);
|
|
p->notify_list = NULL;
|
|
}
|
|
|
|
if (p->timeout_source) {
|
|
g_source_remove(p->timeout_source);
|
|
p->timeout_source = 0;
|
|
}
|
|
}
|
|
|
|
void g_ril_set_disconnect_function(GRil *ril, GRilDisconnectFunc disconnect,
|
|
gpointer user_data)
|
|
{
|
|
ril->parent->user_disconnect = disconnect;
|
|
ril->parent->user_disconnect_data = user_data;
|
|
}
|
|
|
|
static void io_disconnect(gpointer user_data)
|
|
{
|
|
struct ril_s *ril = user_data;
|
|
|
|
ofono_error("%s: disconnected from rild", __func__);
|
|
|
|
ril_cleanup(ril);
|
|
g_ril_io_unref(ril->io);
|
|
ril->io = NULL;
|
|
|
|
if (ril->user_disconnect)
|
|
ril->user_disconnect(ril->user_disconnect_data);
|
|
}
|
|
|
|
static void handle_response(struct ril_s *p, struct ril_msg *message)
|
|
{
|
|
guint count = g_queue_get_length(p->command_queue);
|
|
struct ril_request *req;
|
|
gboolean found = FALSE;
|
|
guint i, len;
|
|
gint id;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
req = g_queue_peek_nth(p->command_queue, i);
|
|
|
|
if (req->id == message->serial_no) {
|
|
found = TRUE;
|
|
message->req = req->req;
|
|
|
|
if (message->error != RIL_E_SUCCESS)
|
|
RIL_TRACE(p, "[%d,%04d]< %s failed %s",
|
|
p->slot, message->serial_no,
|
|
request_id_to_string(p, message->req),
|
|
ril_error_to_string(message->error));
|
|
|
|
req = g_queue_pop_nth(p->command_queue, i);
|
|
if (req->callback)
|
|
req->callback(message, req->user_data);
|
|
|
|
/* gril may have been destroyed in the request callback */
|
|
if (p->destroyed) {
|
|
ril_request_destroy(req);
|
|
return;
|
|
}
|
|
|
|
len = g_queue_get_length(p->out_queue);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
id = GPOINTER_TO_INT(g_queue_peek_nth(
|
|
p->out_queue, i));
|
|
if (id == req->id) {
|
|
g_queue_pop_nth(p->out_queue, i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ril_request_destroy(req);
|
|
|
|
if (g_queue_peek_head(p->command_queue))
|
|
ril_wakeup_writer(p);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == FALSE)
|
|
ofono_error("No matching request for reply: %s serial_no: %d!",
|
|
request_id_to_string(p, message->req),
|
|
message->serial_no);
|
|
|
|
}
|
|
|
|
static gboolean node_check_destroyed(struct ril_notify_node *node,
|
|
gpointer userdata)
|
|
{
|
|
gboolean val = GPOINTER_TO_UINT(userdata);
|
|
|
|
if (node->destroyed == val)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void handle_unsol_req(struct ril_s *p, struct ril_msg *message)
|
|
{
|
|
struct ril_notify *notify;
|
|
|
|
if (p->notify_list == NULL)
|
|
return;
|
|
|
|
p->in_notify = TRUE;
|
|
|
|
notify = g_hash_table_lookup(p->notify_list, &message->req);
|
|
if (notify != NULL) {
|
|
GSList *list_item;
|
|
|
|
for (list_item = notify->nodes; list_item;
|
|
list_item = g_slist_next(list_item)) {
|
|
struct ril_notify_node *node = list_item->data;
|
|
|
|
if (node->destroyed)
|
|
continue;
|
|
|
|
node->callback(message, node->user_data);
|
|
}
|
|
} else {
|
|
/* Only log events not being listended for... */
|
|
DBG("RIL Event slot %d: %s\n",
|
|
p->slot, unsol_request_to_string(p, message->req));
|
|
}
|
|
|
|
p->in_notify = FALSE;
|
|
|
|
/* Now destroy nodes possibly removed by callbacks */
|
|
if (notify != NULL)
|
|
ril_unregister_all(p, FALSE, node_check_destroyed,
|
|
GUINT_TO_POINTER(TRUE));
|
|
}
|
|
|
|
static void dispatch(struct ril_s *p, struct ril_msg *message)
|
|
{
|
|
int32_t *unsolicited_field, *id_num_field;
|
|
gchar *bufp = message->buf;
|
|
gchar *datap;
|
|
gsize data_len;
|
|
|
|
/* This could be done with a struct/union... */
|
|
unsolicited_field = (int32_t *) (void *) bufp;
|
|
if (*unsolicited_field)
|
|
message->unsolicited = TRUE;
|
|
else
|
|
message->unsolicited = FALSE;
|
|
|
|
bufp += 4;
|
|
|
|
id_num_field = (int32_t *) (void *) bufp;
|
|
if (message->unsolicited) {
|
|
message->req = (int) *id_num_field;
|
|
|
|
/*
|
|
* A RIL Unsolicited Event is two UINT32 fields ( unsolicited,
|
|
* and req/ev ), so subtract the length of the header from the
|
|
* overall length to calculate the length of the Event Data.
|
|
*/
|
|
data_len = message->buf_len - 8;
|
|
} else {
|
|
message->serial_no = (int) *id_num_field;
|
|
|
|
bufp += 4;
|
|
message->error = *((int32_t *) (void *) bufp);
|
|
|
|
/*
|
|
* A RIL Solicited Response is three UINT32 fields ( unsolicied,
|
|
* serial_no and error ), so subtract the length of the header
|
|
* from the overall length to calculate the length of the Event
|
|
* Data.
|
|
*/
|
|
data_len = message->buf_len - 12;
|
|
}
|
|
|
|
/* advance to start of data.. */
|
|
bufp += 4;
|
|
|
|
/*
|
|
* Now, use buffer for event data if present
|
|
*/
|
|
if (data_len) {
|
|
datap = g_try_malloc(data_len);
|
|
if (datap == NULL)
|
|
goto error;
|
|
|
|
/* Copy event bytes from message->buf into new buffer */
|
|
memcpy(datap, bufp, data_len);
|
|
|
|
/* Free buffer that includes header */
|
|
g_free(message->buf);
|
|
|
|
/* ...and replace with new buffer */
|
|
message->buf = datap;
|
|
message->buf_len = data_len;
|
|
} else {
|
|
/* Free buffer that includes header */
|
|
g_free(message->buf);
|
|
|
|
/* To know if there was no data when parsing */
|
|
message->buf = NULL;
|
|
message->buf_len = 0;
|
|
}
|
|
|
|
if (message->unsolicited == TRUE)
|
|
handle_unsol_req(p, message);
|
|
else
|
|
handle_response(p, message);
|
|
|
|
error:
|
|
g_free(message->buf);
|
|
g_free(message);
|
|
}
|
|
|
|
static struct ril_msg *read_fixed_record(struct ril_s *p,
|
|
const guchar *bytes, gsize *len)
|
|
{
|
|
struct ril_msg *message;
|
|
unsigned message_len, plen;
|
|
|
|
/* First four bytes are length in TCP byte order (Big Endian) */
|
|
plen = ntohl(*((uint32_t *) (void *) bytes));
|
|
bytes += 4;
|
|
|
|
/*
|
|
* TODO: Verify that 8k is the max message size from rild.
|
|
*
|
|
* This condition shouldn't happen. If it does
|
|
* there are three options:
|
|
*
|
|
* 1) Exit; ofono will restart via DBus (this is what we do now)
|
|
* 2) Consume the bytes & continue
|
|
* 3) force a disconnect
|
|
*/
|
|
if (plen > GRIL_BUFFER_SIZE - 4) {
|
|
ofono_error("ERROR RIL parcel bigger than buffer (%u), exiting",
|
|
plen);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* If we don't have the whole fixed record in the ringbuffer
|
|
* then return NULL & leave ringbuffer as is.
|
|
*/
|
|
|
|
message_len = *len - 4;
|
|
if (message_len < plen)
|
|
return NULL;
|
|
|
|
message = g_malloc(sizeof(struct ril_msg));
|
|
|
|
/* allocate ril_msg->buffer */
|
|
message->buf_len = plen;
|
|
message->buf = g_malloc(plen);
|
|
|
|
/* Copy bytes into message buffer */
|
|
memmove(message->buf, (const void *) bytes, plen);
|
|
|
|
/* Indicate to caller size of record we extracted */
|
|
*len = plen + 4;
|
|
return message;
|
|
}
|
|
|
|
static void new_bytes(struct ring_buffer *rbuf, gpointer user_data)
|
|
{
|
|
struct ril_msg *message;
|
|
struct ril_s *p = user_data;
|
|
unsigned int len = ring_buffer_len(rbuf);
|
|
unsigned int wrap = ring_buffer_len_no_wrap(rbuf);
|
|
guchar *buf = ring_buffer_read_ptr(rbuf, p->read_so_far);
|
|
|
|
p->in_read_handler = TRUE;
|
|
|
|
while (p->suspended == FALSE && (p->read_so_far < len)) {
|
|
gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
|
|
|
|
if (rbytes < 4) {
|
|
DBG("Not enough bytes for header length: len: %d", len);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This function attempts to read the next full length
|
|
* fixed message from the stream. if not all bytes are
|
|
* available, it returns NULL. otherwise it allocates
|
|
* and returns a ril_message with the copied bytes, and
|
|
* drains those bytes from the ring_buffer
|
|
*/
|
|
message = read_fixed_record(p, buf, &rbytes);
|
|
|
|
/* wait for the rest of the record... */
|
|
if (message == NULL)
|
|
break;
|
|
|
|
buf += rbytes;
|
|
p->read_so_far += rbytes;
|
|
|
|
/* TODO: need to better understand how wrap works! */
|
|
if (p->read_so_far == wrap) {
|
|
buf = ring_buffer_read_ptr(rbuf, p->read_so_far);
|
|
wrap = len;
|
|
}
|
|
|
|
dispatch(p, message);
|
|
|
|
ring_buffer_drain(rbuf, p->read_so_far);
|
|
|
|
len -= p->read_so_far;
|
|
wrap -= p->read_so_far;
|
|
p->read_so_far = 0;
|
|
}
|
|
|
|
p->in_read_handler = FALSE;
|
|
|
|
if (p->destroyed)
|
|
g_free(p);
|
|
}
|
|
|
|
/*
|
|
* This function is a GIOFunc and may be called directly or via an IO watch.
|
|
* The return value controls whether the watch stays active ( TRUE ), or is
|
|
* removed ( FALSE ).
|
|
*/
|
|
static gboolean can_write_data(gpointer data)
|
|
{
|
|
struct ril_s *ril = data;
|
|
struct ril_request *req;
|
|
gsize bytes_written, towrite, len;
|
|
guint qlen, oqlen;
|
|
gint id;
|
|
gboolean written = TRUE;
|
|
guint i, j;
|
|
|
|
qlen = g_queue_get_length(ril->command_queue);
|
|
if (qlen < 1)
|
|
return FALSE;
|
|
|
|
/* if the whole request was not written */
|
|
if (ril->req_bytes_written != 0) {
|
|
|
|
for (i = 0; i < qlen; i++) {
|
|
req = g_queue_peek_nth(ril->command_queue, i);
|
|
if (req) {
|
|
id = GPOINTER_TO_INT(g_queue_peek_head(
|
|
ril->out_queue));
|
|
if (req->id == id)
|
|
goto out;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
/* if no requests already sent */
|
|
oqlen = g_queue_get_length(ril->out_queue);
|
|
if (oqlen < 1) {
|
|
req = g_queue_peek_head(ril->command_queue);
|
|
if (req == NULL)
|
|
return FALSE;
|
|
|
|
g_queue_push_head(ril->out_queue, GINT_TO_POINTER(req->id));
|
|
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < qlen; i++) {
|
|
req = g_queue_peek_nth(ril->command_queue, i);
|
|
if (req == NULL)
|
|
return FALSE;
|
|
|
|
for (j = 0; j < oqlen; j++) {
|
|
id = GPOINTER_TO_INT(
|
|
g_queue_peek_nth(ril->out_queue, j));
|
|
if (req->id == id) {
|
|
written = TRUE;
|
|
break;
|
|
} else {
|
|
written = FALSE;
|
|
}
|
|
}
|
|
|
|
if (written == FALSE)
|
|
break;
|
|
}
|
|
|
|
/* watcher fired though requests already written */
|
|
if (written == TRUE)
|
|
return FALSE;
|
|
|
|
g_queue_push_head(ril->out_queue, GINT_TO_POINTER(req->id));
|
|
|
|
out:
|
|
len = req->data_len;
|
|
|
|
towrite = len - ril->req_bytes_written;
|
|
|
|
#ifdef WRITE_SCHEDULER_DEBUG
|
|
if (towrite > 5)
|
|
towrite = 5;
|
|
#endif
|
|
|
|
bytes_written = g_ril_io_write(ril->io,
|
|
req->data + ril->req_bytes_written,
|
|
towrite);
|
|
|
|
if (bytes_written == 0)
|
|
return FALSE;
|
|
|
|
ril->req_bytes_written += bytes_written;
|
|
if (bytes_written < towrite)
|
|
return TRUE;
|
|
else
|
|
ril->req_bytes_written = 0;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void ril_wakeup_writer(struct ril_s *ril)
|
|
{
|
|
g_ril_io_set_write_handler(ril->io, can_write_data, ril);
|
|
}
|
|
|
|
static void ril_suspend(struct ril_s *ril)
|
|
{
|
|
ril->suspended = TRUE;
|
|
|
|
g_ril_io_set_write_handler(ril->io, NULL, NULL);
|
|
g_ril_io_set_read_handler(ril->io, NULL, NULL);
|
|
g_ril_io_set_debug(ril->io, NULL, NULL);
|
|
}
|
|
|
|
static gboolean ril_set_debug(struct ril_s *ril,
|
|
GRilDebugFunc func, gpointer user_data)
|
|
{
|
|
if (ril->io == NULL)
|
|
return FALSE;
|
|
|
|
g_ril_io_set_debug(ril->io, func, user_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void ril_unref(struct ril_s *ril)
|
|
{
|
|
gboolean is_zero;
|
|
|
|
is_zero = g_atomic_int_dec_and_test(&ril->ref_count);
|
|
|
|
if (is_zero == FALSE)
|
|
return;
|
|
|
|
if (ril->io) {
|
|
ril_suspend(ril);
|
|
g_ril_io_unref(ril->io);
|
|
ril->io = NULL;
|
|
ril_cleanup(ril);
|
|
}
|
|
|
|
if (ril->in_read_handler)
|
|
ril->destroyed = TRUE;
|
|
else
|
|
g_free(ril);
|
|
}
|
|
|
|
static gboolean node_compare_by_group(struct ril_notify_node *node,
|
|
gpointer userdata)
|
|
{
|
|
guint group = GPOINTER_TO_UINT(userdata);
|
|
|
|
if (node->gid == group)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static struct ril_s *create_ril(const char *sock_path, unsigned int uid,
|
|
unsigned int gid)
|
|
|
|
{
|
|
struct ril_s *ril;
|
|
struct sockaddr_un addr;
|
|
int sk;
|
|
int r;
|
|
GIOChannel *io;
|
|
|
|
ril = g_try_new0(struct ril_s, 1);
|
|
if (ril == NULL)
|
|
return ril;
|
|
|
|
ril->ref_count = 1;
|
|
ril->next_cmd_id = 1;
|
|
ril->next_notify_id = 1;
|
|
ril->next_gid = 0;
|
|
ril->req_bytes_written = 0;
|
|
ril->trace = FALSE;
|
|
|
|
/* sock_path is allowed to be NULL for unit tests */
|
|
if (sock_path == NULL)
|
|
return ril;
|
|
|
|
sk = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (sk < 0) {
|
|
ofono_error("create_ril: can't create unix socket: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
goto error;
|
|
}
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1);
|
|
|
|
/* Drop root user last, otherwise we won't be able to change egid */
|
|
if (gid != 0 && setegid(gid) < 0)
|
|
ofono_error("%s: setegid(%d) failed: %s (%d)",
|
|
__func__, gid, strerror(errno), errno);
|
|
|
|
if (uid != 0 && seteuid(uid) < 0)
|
|
ofono_error("%s: seteuid(%d) failed: %s (%d)",
|
|
__func__, uid, strerror(errno), errno);
|
|
|
|
r = connect(sk, (struct sockaddr *) &addr, sizeof(addr));
|
|
|
|
/* Switch back to root as needed */
|
|
if (uid && seteuid(0) < 0)
|
|
ofono_error("%s: seteuid(0) failed: %s (%d)",
|
|
__func__, strerror(errno), errno);
|
|
|
|
if (gid && setegid(0) < 0)
|
|
ofono_error("%s: setegid(0) failed: %s (%d)",
|
|
__func__, strerror(errno), errno);
|
|
|
|
if (r < 0) {
|
|
ofono_error("create_ril: can't connect to RILD: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
goto error;
|
|
}
|
|
|
|
io = g_io_channel_unix_new(sk);
|
|
if (io == NULL) {
|
|
ofono_error("create_ril: can't open RILD io channel: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
goto error;
|
|
}
|
|
|
|
g_io_channel_set_close_on_unref(io, TRUE);
|
|
g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL);
|
|
|
|
ril->io = g_ril_io_new(io);
|
|
g_io_channel_unref(io);
|
|
|
|
if (ril->io == NULL) {
|
|
ofono_error("create_ril: can't create ril->io");
|
|
goto error;
|
|
}
|
|
|
|
g_ril_io_set_disconnect_function(ril->io, io_disconnect, ril);
|
|
|
|
ril->command_queue = g_queue_new();
|
|
if (ril->command_queue == NULL) {
|
|
ofono_error("create_ril: Couldn't create command_queue.");
|
|
goto error;
|
|
}
|
|
|
|
ril->out_queue = g_queue_new();
|
|
if (ril->out_queue == NULL) {
|
|
ofono_error("create_ril: Couldn't create out_queue.");
|
|
goto error;
|
|
}
|
|
|
|
ril->notify_list = g_hash_table_new_full(g_int_hash, g_int_equal,
|
|
g_free,
|
|
ril_notify_destroy);
|
|
|
|
g_ril_io_set_read_handler(ril->io, new_bytes, ril);
|
|
|
|
return ril;
|
|
|
|
error:
|
|
ril_unref(ril);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct ril_notify *ril_notify_create(struct ril_s *ril,
|
|
const int req)
|
|
{
|
|
struct ril_notify *notify;
|
|
int *key;
|
|
|
|
notify = g_try_new0(struct ril_notify, 1);
|
|
if (notify == NULL)
|
|
return 0;
|
|
|
|
key = g_try_new0(int, 1);
|
|
if (key == NULL)
|
|
return 0;
|
|
|
|
*key = req;
|
|
|
|
g_hash_table_insert(ril->notify_list, key, notify);
|
|
|
|
return notify;
|
|
}
|
|
|
|
static void ril_cancel_group(struct ril_s *ril, guint group)
|
|
{
|
|
int n = 0;
|
|
guint len, i;
|
|
struct ril_request *req;
|
|
gboolean sent;
|
|
|
|
if (ril->command_queue == NULL)
|
|
return;
|
|
|
|
while ((req = g_queue_peek_nth(ril->command_queue, n)) != NULL) {
|
|
if (req->id == 0 || req->gid != group) {
|
|
n += 1;
|
|
continue;
|
|
}
|
|
|
|
req->callback = NULL;
|
|
sent = FALSE;
|
|
|
|
len = g_queue_get_length(ril->out_queue);
|
|
for (i = 0; i < len; i++) {
|
|
if (GPOINTER_TO_INT(
|
|
g_queue_peek_nth(ril->out_queue, i))
|
|
== req->id) {
|
|
n += 1;
|
|
sent = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sent)
|
|
continue;
|
|
|
|
g_queue_remove(ril->command_queue, req);
|
|
ril_request_destroy(req);
|
|
}
|
|
}
|
|
|
|
static guint ril_register(struct ril_s *ril, guint group,
|
|
const int req, GRilNotifyFunc func,
|
|
gpointer user_data)
|
|
{
|
|
struct ril_notify *notify;
|
|
struct ril_notify_node *node;
|
|
|
|
if (ril->notify_list == NULL)
|
|
return 0;
|
|
|
|
if (func == NULL)
|
|
return 0;
|
|
|
|
notify = g_hash_table_lookup(ril->notify_list, &req);
|
|
|
|
if (notify == NULL)
|
|
notify = ril_notify_create(ril, req);
|
|
|
|
if (notify == NULL)
|
|
return 0;
|
|
|
|
node = g_try_new0(struct ril_notify_node, 1);
|
|
if (node == NULL)
|
|
return 0;
|
|
|
|
node->id = ril->next_notify_id++;
|
|
node->gid = group;
|
|
node->callback = func;
|
|
node->user_data = user_data;
|
|
|
|
notify->nodes = g_slist_prepend(notify->nodes, node);
|
|
|
|
return node->id;
|
|
}
|
|
|
|
static gboolean ril_unregister(struct ril_s *ril, gboolean mark_only,
|
|
guint group, guint id)
|
|
{
|
|
GHashTableIter iter;
|
|
struct ril_notify *notify;
|
|
struct ril_notify_node *node;
|
|
gpointer key, value;
|
|
GSList *l;
|
|
|
|
if (ril->notify_list == NULL)
|
|
return FALSE;
|
|
|
|
g_hash_table_iter_init(&iter, ril->notify_list);
|
|
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
notify = value;
|
|
|
|
l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
|
|
ril_notify_node_compare_by_id);
|
|
|
|
if (l == NULL)
|
|
continue;
|
|
|
|
node = l->data;
|
|
|
|
if (node->gid != group)
|
|
return FALSE;
|
|
|
|
if (mark_only) {
|
|
node->destroyed = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
ril_notify_node_destroy(node, NULL);
|
|
notify->nodes = g_slist_remove(notify->nodes, node);
|
|
|
|
if (notify->nodes == NULL)
|
|
g_hash_table_iter_remove(&iter);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void g_ril_init_parcel(const struct ril_msg *message, struct parcel *rilp)
|
|
{
|
|
/* Set up Parcel struct for proper parsing */
|
|
rilp->data = message->buf;
|
|
rilp->size = message->buf_len;
|
|
rilp->capacity = message->buf_len;
|
|
rilp->offset = 0;
|
|
rilp->malformed = 0;
|
|
}
|
|
|
|
GRil *g_ril_new_with_ucred(const char *sock_path, enum ofono_ril_vendor vendor,
|
|
unsigned int uid, unsigned int gid)
|
|
{
|
|
GRil *ril;
|
|
|
|
ril = g_try_new0(GRil, 1);
|
|
if (ril == NULL)
|
|
return NULL;
|
|
|
|
ril->parent = create_ril(sock_path, uid, gid);
|
|
if (ril->parent == NULL) {
|
|
g_free(ril);
|
|
return NULL;
|
|
}
|
|
|
|
ril->group = ril->parent->next_gid++;
|
|
ril->ref_count = 1;
|
|
|
|
ril->parent->vendor = vendor;
|
|
|
|
return ril;
|
|
}
|
|
|
|
GRil *g_ril_new(const char *sock_path, enum ofono_ril_vendor vendor)
|
|
{
|
|
return g_ril_new_with_ucred(sock_path, vendor, 0, 0);
|
|
}
|
|
|
|
GRil *g_ril_clone(GRil *clone)
|
|
{
|
|
GRil *ril;
|
|
|
|
if (clone == NULL)
|
|
return NULL;
|
|
|
|
ril = g_try_new0(GRil, 1);
|
|
if (ril == NULL)
|
|
return NULL;
|
|
|
|
ril->parent = clone->parent;
|
|
ril->group = ril->parent->next_gid++;
|
|
ril->ref_count = 1;
|
|
g_atomic_int_inc(&ril->parent->ref_count);
|
|
|
|
return ril;
|
|
}
|
|
|
|
GIOChannel *g_ril_get_channel(GRil *ril)
|
|
{
|
|
if (ril == NULL || ril->parent->io == NULL)
|
|
return NULL;
|
|
|
|
return g_ril_io_get_channel(ril->parent->io);
|
|
|
|
}
|
|
|
|
GRilIO *g_ril_get_io(GRil *ril)
|
|
{
|
|
if (ril == NULL)
|
|
return NULL;
|
|
|
|
return ril->parent->io;
|
|
}
|
|
|
|
GRil *g_ril_ref(GRil *ril)
|
|
{
|
|
if (ril == NULL)
|
|
return NULL;
|
|
|
|
g_atomic_int_inc(&ril->ref_count);
|
|
|
|
return ril;
|
|
}
|
|
|
|
gint g_ril_send(GRil *ril, const gint reqid, struct parcel *rilp,
|
|
GRilResponseFunc func, gpointer user_data,
|
|
GDestroyNotify notify)
|
|
{
|
|
struct ril_request *r;
|
|
struct ril_s *p;
|
|
|
|
if (ril == NULL
|
|
|| ril->parent == NULL
|
|
|| ril->parent->command_queue == NULL)
|
|
return 0;
|
|
|
|
p = ril->parent;
|
|
|
|
r = ril_request_create(p, ril->group, reqid, p->next_cmd_id, rilp,
|
|
func, user_data, notify, FALSE);
|
|
|
|
if (rilp != NULL)
|
|
parcel_free(rilp);
|
|
|
|
if (r == NULL)
|
|
return 0;
|
|
|
|
p->next_cmd_id++;
|
|
|
|
g_queue_push_tail(p->command_queue, r);
|
|
|
|
ril_wakeup_writer(p);
|
|
|
|
if (rilp == NULL)
|
|
g_ril_print_request_no_args(ril, r->id, reqid);
|
|
else
|
|
g_ril_print_request(ril, r->id, reqid);
|
|
|
|
return r->id;
|
|
}
|
|
|
|
void g_ril_unref(GRil *ril)
|
|
{
|
|
gboolean is_zero;
|
|
|
|
if (ril == NULL)
|
|
return;
|
|
|
|
is_zero = g_atomic_int_dec_and_test(&ril->ref_count);
|
|
|
|
if (is_zero == FALSE)
|
|
return;
|
|
|
|
ril_cancel_group(ril->parent, ril->group);
|
|
g_ril_unregister_all(ril);
|
|
ril_unref(ril->parent);
|
|
|
|
g_free(ril);
|
|
}
|
|
|
|
gboolean g_ril_get_trace(GRil *ril)
|
|
{
|
|
|
|
if (ril == NULL || ril->parent == NULL)
|
|
return FALSE;
|
|
|
|
return ril->parent->trace;
|
|
}
|
|
|
|
gboolean g_ril_set_trace(GRil *ril, gboolean trace)
|
|
{
|
|
|
|
if (ril == NULL || ril->parent == NULL)
|
|
return FALSE;
|
|
|
|
return ril->parent->trace = trace;
|
|
}
|
|
|
|
gboolean g_ril_set_slot(GRil *ril, int slot)
|
|
{
|
|
if (ril == NULL || ril->parent == NULL)
|
|
return FALSE;
|
|
|
|
ril->parent->slot = slot;
|
|
return TRUE;
|
|
}
|
|
|
|
int g_ril_get_slot(GRil *ril)
|
|
{
|
|
if (ril == NULL)
|
|
return 0;
|
|
|
|
return ril->parent->slot;
|
|
}
|
|
|
|
gboolean g_ril_set_debugf(GRil *ril,
|
|
GRilDebugFunc func, gpointer user_data)
|
|
{
|
|
|
|
if (ril == NULL || ril->group != 0)
|
|
return FALSE;
|
|
|
|
return ril_set_debug(ril->parent, func, user_data);
|
|
}
|
|
|
|
gboolean g_ril_set_vendor_print_msg_id_funcs(GRil *ril,
|
|
GRilMsgIdToStrFunc req_to_string,
|
|
GRilMsgIdToStrFunc unsol_to_string)
|
|
{
|
|
if (ril == NULL || ril->parent == NULL)
|
|
return FALSE;
|
|
|
|
ril->parent->req_to_string = req_to_string;
|
|
ril->parent->unsol_to_string = unsol_to_string;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
guint g_ril_register(GRil *ril, const int req,
|
|
GRilNotifyFunc func, gpointer user_data)
|
|
{
|
|
if (ril == NULL)
|
|
return 0;
|
|
|
|
return ril_register(ril->parent, ril->group, req,
|
|
func, user_data);
|
|
}
|
|
|
|
gboolean g_ril_unregister(GRil *ril, guint id)
|
|
{
|
|
if (ril == NULL)
|
|
return FALSE;
|
|
|
|
return ril_unregister(ril->parent, ril->parent->in_notify,
|
|
ril->group, id);
|
|
}
|
|
|
|
gboolean g_ril_unregister_all(GRil *ril)
|
|
{
|
|
if (ril == NULL)
|
|
return FALSE;
|
|
|
|
return ril_unregister_all(ril->parent,
|
|
ril->parent->in_notify,
|
|
node_compare_by_group,
|
|
GUINT_TO_POINTER(ril->group));
|
|
}
|
|
|
|
enum ofono_ril_vendor g_ril_vendor(GRil *ril)
|
|
{
|
|
if (ril == NULL)
|
|
return OFONO_RIL_VENDOR_AOSP;
|
|
|
|
return ril->parent->vendor;
|
|
}
|
|
|
|
const char *g_ril_request_id_to_string(GRil *ril, int req)
|
|
{
|
|
return request_id_to_string(ril->parent, req);
|
|
}
|
|
|
|
const char *g_ril_unsol_request_to_string(GRil *ril, int req)
|
|
{
|
|
return unsol_request_to_string(ril->parent, req);
|
|
}
|