diff options
author | Rémi Denis-Courmont <remi.denis-courmont@nokia.com> | 2009-06-09 16:26:36 +0300 |
---|---|---|
committer | Aki Niemi <aki.niemi@nokia.com> | 2009-06-11 21:50:10 +0300 |
commit | 0661810b018adb9735485f98cee0e4f8c3aee340 (patch) | |
tree | 820092956dc575bd7284b9d1dc73232bb8c332a0 | |
parent | 667655df7995feda7cd07504f1ba30f397578644 (diff) | |
download | ofono-0661810b018adb9735485f98cee0e4f8c3aee340.tar.bz2 |
Phonet/ISI client helper
Signed-off-by: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
-rw-r--r-- | isi/Makefile.am | 3 | ||||
-rw-r--r-- | isi/client.c | 360 | ||||
-rw-r--r-- | isi/client.h | 48 |
3 files changed, 410 insertions, 1 deletions
diff --git a/isi/Makefile.am b/isi/Makefile.am index e7e13e7e..10b325c8 100644 --- a/isi/Makefile.am +++ b/isi/Makefile.am @@ -5,5 +5,6 @@ MAINTAINERCLEANFILES = Makefile.in libgisi_la_SOURCES = \ netlink.h netlink.c \ - socket.h socket.c + socket.h socket.c \ + client.h client.c libgisi_la_LIBADD = @GLIB_LIBS@ diff --git a/isi/client.c b/isi/client.c new file mode 100644 index 00000000..f8f01711 --- /dev/null +++ b/isi/client.c @@ -0,0 +1,360 @@ +/* + * This file is part of oFono - Open Source Telephony + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Rémi Denis-Courmont <remi.denis-courmont@nokia.com> + * + * 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 <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <errno.h> +#include <glib.h> + +#include "socket.h" +#include "client.h" + +struct isi_client { + uint8_t resource; + + /* Requests */ + int fd; + guint source; + uint8_t prev[256], next[256]; + guint timeout[256]; + isi_client_cb_t func[256]; + void *data[256]; + + /* Indications */ + struct { + int fd; + guint source; + uint16_t count; + isi_ind_cb_t func[256]; + void *data[256]; + } ind; +}; + +static gboolean isi_callback(GIOChannel *, GIOCondition, gpointer); +static gboolean isi_timeout(gpointer); + +static inline struct isi_request *isi_req(struct isi_client *cl, uint8_t id) +{ + return (struct isi_request *)(((uint8_t *)(void *)cl) + id); +} + +static inline uint8_t isi_id(void *ptr) +{ + return ((uintptr_t)ptr) & 255; +} + +static inline struct isi_client *isi_cl(void *ptr) +{ + return (struct isi_client *)(((uintptr_t)ptr) & ~255); +} + +/** + * Create an ISI client. + * @param resource Phonet resource ID for the client + * @return NULL on error (see errno), an isi_client pointer on success, + */ +struct isi_client *isi_client_create(uint8_t resource) +{ + void *ptr; + struct isi_client *cl; + GIOChannel *channel; + unsigned i; + + if (G_UNLIKELY(posix_memalign(&ptr, 256, sizeof(*cl)))) + abort(); + cl = ptr; + cl->resource = resource; + memset(cl->timeout, 0, sizeof(cl->timeout)); + for (i = 0; i < 256; i++) { + cl->data[i] = cl->ind.data[i] = NULL; + cl->func[i] = NULL; + cl->ind.func[i] = NULL; + } + cl->ind.count = 0; + + /* Reserve 0 as head of available IDs, and 255 as head of busy ones */ + cl->prev[0] = 254; + for (i = 0; i < 254; i++) { + cl->next[i] = i + 1; + cl->prev[i + 1] = i; + } + cl->next[254] = 0; + cl->prev[255] = cl->next[255] = 255; + + channel = phonet_new(resource); + if (channel == NULL) { + free(cl); + return NULL; + } + cl->fd = g_io_channel_unix_get_fd(channel); + cl->source = g_io_add_watch(channel, + G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, + isi_callback, cl); + g_io_channel_unref(channel); + return cl; +} + +/** + * Destroys an ISI client, cancels all pending transactions and subscriptions. + * @param client client to destroy + */ +void isi_client_destroy(struct isi_client *client) +{ + unsigned id; + + g_source_remove(client->source); + for (id = 0; id < 256; id++) + if (client->timeout[id] > 0) + g_source_remove(client->timeout[id]); + if (client->ind.count > 0) + g_source_remove(client->ind.source); + free(client); +} + +/** + * Make an ISI request and register a callback to process the response(s) to + * the resulting transaction. + * @param cl ISI client (from isi_client_create()) + * @param buf pointer to request payload + * @param len request payload byte length + * @param cb callback to process response(s) + * @param opaque data for the callback + */ +struct isi_request *isi_request_make(struct isi_client *cl, + const void *__restrict buf, size_t len, + unsigned timeout, + isi_client_cb_t cb, void *opaque) +{ + struct iovec iov[2]; + ssize_t ret; + uint8_t id = cl->next[0]; + + if (id == 0) { + errno = EBUSY; + return NULL; + } + if (cb == NULL) { + errno = EINVAL; + return NULL; + } + iov[0].iov_base = &id; + iov[0].iov_len = 1; + iov[1].iov_base = (void *)buf; + iov[1].iov_len = len; + ret = writev(cl->fd, iov, sizeof(iov) / sizeof(iov[0])); + if (ret == -1) + return NULL; + if (ret != (ssize_t)(len + 2)) { + errno = EMSGSIZE; + return NULL; + } + + cl->func[id] = cb; + cl->data[id] = opaque; + + /* Remove transaction from available list */ + cl->next[0] = cl->next[id]; + cl->prev[cl->next[id]] = 0; + /* Insert into busy list */ + cl->next[id] = cl->next[255]; + cl->prev[cl->next[id]] = id; + cl->next[255] = id; + cl->prev[id] = 255; + + if (timeout > 0) + cl->timeout[id] = g_timeout_add_seconds(timeout, + isi_timeout, cl); + else + cl->timeout[id] = 0; + return isi_req(cl, id); +} + +/** + * Cancels a pending request, i.e. stop waiting for responses and cancels the + * timeout. + * @param req request to cancel + */ +void isi_request_cancel(struct isi_request *req) +{ + struct isi_client *cl = isi_cl(req); + uint8_t id = isi_id(req); + + cl->func[id] = NULL; + cl->data[id] = NULL; + + /* Remove transaction from pending circular list */ + cl->prev[cl->next[id]] = cl->prev[id]; + cl->next[cl->prev[id]] = cl->next[id]; + /* Insert transaction into available circular list */ + cl->prev[id] = cl->prev[0]; + cl->prev[0] = id; + cl->next[id] = 0; + cl->next[cl->prev[id]] = id; + + if (cl->timeout[id] > 0) { + g_source_remove(cl->timeout[id]); + cl->timeout[id] = 0; + } +} + +#define PN_COMMGR 0x10 +#define PNS_SUBSCRIBED_RESOURCES_IND 0x10 + +static int isi_indication_init(struct isi_client *cl) +{ + uint8_t msg[] = { + 0, PNS_SUBSCRIBED_RESOURCES_IND, 1, cl->resource, + }; + GIOChannel *channel = phonet_new(PN_COMMGR); + + if (channel == NULL) + return errno; + /* Send subscribe indication */ + cl->ind.fd = g_io_channel_unix_get_fd(channel); + send(cl->ind.fd, msg, 4, 0); + cl->ind.source = g_io_add_watch(channel, + G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, + isi_callback, cl); + return 0; +} + +static void isi_indication_deinit(struct isi_client *client) +{ + uint8_t msg[] = { + 0, PNS_SUBSCRIBED_RESOURCES_IND, 0, + }; + + /* Send empty subscribe indication */ + send(client->ind.fd, msg, 3, 0); + g_source_remove(client->ind.source); +} + +/** + * Subscribe to a given indication type for the resource that an ISI client + * is associated with. If the same type was already subscrived, the old + * subscription is overriden. + * @param cl ISI client (fomr isi_client_create()) + * @param type indication type + * @param cb callback to process received indications + * @param data data for the callback + * @return 0 on success, a system error code otherwise. + */ +int isi_subscribe(struct isi_client *cl, uint8_t type, + isi_ind_cb_t cb, void *data) +{ + if (cb == NULL) + return EINVAL; + + if (cl->ind.func[type] == NULL) { + if (cl->ind.count == 0) { + int ret = isi_indication_init(cl); + if (ret) + return ret; + } + cl->ind.count++; + } + cl->ind.func[type] = cb; + cl->ind.data[type] = data; + return 0; +} + +/** + * Unsubscribe from a given indication type. + * @param client ISI client (from isi_client_create()) + * @param type indication type. + */ +void isi_unsubscribe(struct isi_client *client, uint8_t type) +{ + /* Unsubscribe */ + if (client->ind.func[type] == NULL) + return; + client->ind.func[type] = NULL; + if (--client->ind.count == 0) + isi_indication_deinit(client); +} + +/* Data callback for both responses and indications */ +static gboolean isi_callback(GIOChannel *channel, GIOCondition cond, + gpointer data) +{ + struct isi_client *cl = data; + int fd = g_io_channel_unix_get_fd(channel); + bool indication = (fd != cl->fd); + int len; + + if (cond & (G_IO_NVAL|G_IO_HUP)) { + g_warning("Unexpected event on Phonet channel %p", channel); + return FALSE; + } + + len = phonet_peek_length(channel); + { + uint32_t buf[(len + 3) / 4]; + uint16_t obj; + uint8_t res, id; + + len = phonet_read(channel, buf, len, &obj, &res); + if (len < 2 || res != cl->resource) + return TRUE; + memcpy(&id, buf, 1); /* Transaction ID or indication type */ + if (indication) { + if (cl->ind.func[id] == NULL) + return TRUE; /* Unsubscribed indication */ + cl->ind.func[id](cl, buf + 1, len - 1, obj, + cl->ind.data[id]); + } else { + if (cl->func[id] == NULL) + return TRUE; /* Bad transaction ID */ + if ((cl->func[id])(cl, buf + 1, len - 1, obj, + cl->data[id])) + isi_request_cancel(isi_req(cl, id)); + } + } + return TRUE; +} + +static gboolean isi_timeout(gpointer data) +{ + struct isi_request *req = data; + struct isi_client *cl = isi_cl(req); + uint8_t id = isi_id(req); + + assert(cl->func[id]); + (cl->func[id])(cl, NULL, 0, 0, cl->data[id]); + isi_request_cancel(req); + return FALSE; +} + +int isi_client_error(const struct isi_client *client) +{ /* The only possible error at the moment */ + return ETIMEDOUT; +} diff --git a/isi/client.h b/isi/client.h new file mode 100644 index 00000000..0632fdc4 --- /dev/null +++ b/isi/client.h @@ -0,0 +1,48 @@ +/* + * This file is part of oFono - Open Source Telephony + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Rémi Denis-Courmont <remi.denis-courmont@nokia.com> + * + * 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 + * + */ + +#ifndef OFONO_PHONET_CLIENT_H +#define OFONO_PHONET_CLIENT_H 1 +#include <stdint.h> +#include <stdbool.h> + +struct isi_client; +struct isi_client *isi_client_create(uint8_t resource); +void isi_client_destroy(struct isi_client *client); +int isi_client_error(const struct isi_client *client); + +typedef bool (*isi_client_cb_t)(struct isi_client *client, + const void *restrict data, size_t len, + uint16_t object, void *opaque); +struct isi_request; +struct isi_request *isi_request_make(struct isi_client *, const void *, size_t, + unsigned timeout, isi_client_cb_t, void *); +void isi_request_cancel(struct isi_request *req); + +typedef void (*isi_ind_cb_t) (struct isi_client *client, + const void *restrict data, size_t len, + uint16_t object, void *opaque); +int isi_subscribe(struct isi_client *client, uint8_t type, + isi_ind_cb_t, void *); +void isi_unsubscribe(struct isi_client *client, uint8_t type); +#endif |