3 * oFono - Open Source Telephony
5 * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/ioctl.h>
42 #define ISIDBG(m, fmt, ...) \
43 if ((m) != NULL && (m)->debug != NULL) \
44 m->debug("gisi: "fmt, ##__VA_ARGS__);
46 struct _GIsiServiceMux {
53 unsigned subscriptions;
54 unsigned registrations;
56 gboolean version_pending;
58 typedef struct _GIsiServiceMux GIsiServiceMux;
74 enum GIsiMessageType type;
75 GIsiServiceMux *service;
77 GIsiNotifyFunc notify;
78 GDestroyNotify destroy;
84 static const struct sockaddr_pn namesrv = {
85 .spn_family = AF_PHONET,
86 .spn_resource = PN_NAMESERVICE,
89 static const struct sockaddr_pn commgr = {
90 .spn_family = AF_PHONET,
91 .spn_resource = PN_COMMGR,
94 static GIsiServiceMux *service_get(GIsiModem *modem, uint8_t resource)
99 mux = g_hash_table_lookup(modem->services, GINT_TO_POINTER(key));
103 mux = g_try_new0(GIsiServiceMux, 1);
107 g_hash_table_insert(modem->services, GINT_TO_POINTER(key), mux);
110 mux->resource = resource;
111 mux->version.major = -1;
112 mux->version.minor = -1;
113 mux->reachable = FALSE;
114 mux->version_pending = FALSE;
119 static gint utid_equal(gconstpointer a, gconstpointer b)
121 const GIsiPending *pa = a;
122 const GIsiPending *pb = b;
124 return pa->utid - pb->utid;
127 static const char *pend_type_to_str(enum GIsiMessageType type)
130 case GISI_MESSAGE_TYPE_REQ:
132 case GISI_MESSAGE_TYPE_IND:
134 case GISI_MESSAGE_TYPE_NTF:
136 case GISI_MESSAGE_TYPE_RESP:
138 case GISI_MESSAGE_TYPE_COMMON:
144 static void pending_dispatch(GIsiPending *pend, GIsiMessage *msg)
148 if (pend->notify == NULL)
151 modem = pend->service->modem;
153 ISIDBG(modem, "%s %s to %p [res=0x%02X, id=0x%02X, utid=0x%02X]",
154 g_isi_msg_strerror(msg), pend_type_to_str(pend->type), pend,
155 g_isi_msg_resource(msg), g_isi_msg_id(msg),
156 g_isi_msg_utid(msg));
158 pend->notify(msg, pend->data);
161 static void service_dispatch(GIsiServiceMux *mux, GIsiMessage *msg,
162 gboolean is_indication)
164 uint8_t msgid = g_isi_msg_id(msg);
165 uint8_t utid = g_isi_msg_utid(msg);
167 GSList *l = mux->pending;
170 GSList *next = l->next;
171 GIsiPending *pend = l->data;
175 * REQs, NTFs and INDs are dispatched on message ID. While
176 * INDs have the unique transaction ID set to zero, NTFs
177 * typically mirror the UTID of the request that set up the
178 * session, and REQs can naturally have any transaction ID.
180 * RESPs are dispatched on unique transaction ID, explicitly
181 * ignoring the msgid. A RESP also completes a transaction,
182 * so it needs to be removed after being notified of.
184 * Version query responses are dispatched in a similar fashion
185 * as RESPs, but based on the pending type and the message ID.
186 * Some of these may be synthesized, but nevertheless need to
189 if (pend->type < GISI_MESSAGE_TYPE_RESP
190 && pend->msgid == msgid) {
192 pending_dispatch(pend, msg);
194 } else if (pend->type == GISI_MESSAGE_TYPE_RESP &&
195 !is_indication && pend->utid == utid) {
197 pending_dispatch(pend, msg);
200 g_isi_pending_remove(pend);
203 } else if (pend->type == GISI_MESSAGE_TYPE_COMMON &&
204 msgid == COMMON_MESSAGE &&
205 pend->msgid == COMM_ISI_VERSION_GET_REQ) {
207 pending_dispatch(pend, msg);
210 g_isi_pending_remove(pend);
217 static void common_message_decode(GIsiServiceMux *mux, GIsiMessage *msg)
223 if (!g_isi_msg_data_get_byte(msg, 0, &code))
227 case COMM_ISA_ENTITY_NOT_REACHABLE_RESP:
228 mux->reachable = FALSE;
232 case COMM_ISI_VERSION_GET_RESP:
234 if (g_isi_msg_data_get_byte(msg, 1, &major) &&
235 g_isi_msg_data_get_byte(msg, 2, &minor)) {
236 mux->version.major = major;
237 mux->version.minor = minor;
243 * PN_SIM doesn't support ISI version, but sends a
244 * 0x00 message as a response. Work around this modem
247 mux->object = g_isi_msg_object(msg);
248 mux->version_pending = FALSE;
249 mux->reachable = TRUE;
252 msg->version = &mux->version;
255 static void firewall_notify_handle(GIsiModem *modem, GIsiMessage *msg)
259 if (!g_isi_msg_data_get_byte(msg, 0, &id))
262 ISIDBG(modem, "firewall blocked message 0x%02X", id);
265 static gboolean isi_callback(GIOChannel *channel, GIOCondition cond,
268 GIsiModem *modem = data;
272 if (cond & (G_IO_NVAL|G_IO_HUP)) {
273 ISIDBG(modem, "Unexpected event on PhoNet channel %p", channel);
277 fd = g_io_channel_unix_get_fd(channel);
278 len = g_isi_phonet_peek_length(channel);
281 struct sockaddr_pn addr;
282 uint32_t buf[(len + 3) / 4];
288 len = g_isi_phonet_read(channel, buf, len, &addr);
297 if (modem->trace != NULL)
298 modem->trace(&msg, NULL);
300 key = addr.spn_resource;
301 mux = g_hash_table_lookup(modem->services, GINT_TO_POINTER(key));
304 * Unfortunately, the FW report has the wrong
305 * resource ID in the N900 modem.
307 if (key == PN_FIREWALL)
308 firewall_notify_handle(modem, &msg);
313 msg.version = &mux->version;
315 if (g_isi_msg_id(&msg) == COMMON_MESSAGE)
316 common_message_decode(mux, &msg);
318 service_dispatch(mux, &msg, fd == modem->ind_fd);
323 static gboolean modem_subs_update(gpointer data)
326 gpointer keyptr, value;
328 GIsiModem *modem = data;
329 uint8_t msg[3 + 256] = {
330 0, PNS_SUBSCRIBED_RESOURCES_IND,
335 modem->subs_source = 0;
337 g_hash_table_iter_init(&iter, modem->services);
339 while (g_hash_table_iter_next(&iter, &keyptr, &value)) {
340 GIsiServiceMux *mux = value;
342 if (mux->subscriptions > 0) {
343 msg[3 + count] = mux->resource;
349 sendto(modem->ind_fd, msg, 3 + msg[2], MSG_NOSIGNAL, (void *)&commgr,
355 static void modem_subs_update_when_idle(GIsiModem *modem)
357 if (modem->subs_source > 0)
360 modem->subs_source = g_idle_add(modem_subs_update, modem);
363 static void service_name_register(GIsiServiceMux *mux)
366 0, PNS_NAME_ADD_REQ, 0, 0,
367 0, 0, 0, mux->resource, /* 32-bit Big-Endian name */
368 0, 0, /* device/object */
373 if (ioctl(mux->modem->req_fd, SIOCPNGETOBJECT, &object) < 0) {
374 ISIDBG(mux->modem, "ioctl(SIOCPNGETOBJECT): %s",
379 /* Fill in the object ID */
380 msg[8] = object >> 8;
381 msg[9] = object & 0xFF;
383 sendto(mux->modem->req_fd, msg, sizeof(msg), MSG_NOSIGNAL,
384 (void *)&namesrv, sizeof(namesrv));
387 static void service_name_deregister(GIsiServiceMux *mux)
389 const uint8_t msg[] = {
390 0, PNS_NAME_REMOVE_REQ, 0, 0,
391 0, 0, 0, mux->resource,
394 sendto(mux->modem->req_fd, msg, sizeof(msg), MSG_NOSIGNAL,
395 (void *)&namesrv, sizeof(namesrv));
398 static void pending_destroy(gpointer value, gpointer user)
400 GIsiPending *op = value;
406 g_source_remove(op->timeout);
408 if (op->destroy != NULL)
409 op->destroy(op->data);
414 static void service_finalize(gpointer value)
416 GIsiServiceMux *mux = value;
417 GIsiModem *modem = mux->modem;
419 if (mux->subscriptions > 0)
420 modem_subs_update_when_idle(modem);
422 if (mux->registrations > 0)
423 service_name_deregister(mux);
425 g_slist_foreach(mux->pending, pending_destroy, NULL);
426 g_slist_free(mux->pending);
430 GIsiModem *g_isi_modem_create(unsigned index)
441 modem = g_try_new0(GIsiModem, 1);
447 inds = g_isi_phonet_new(index);
448 reqs = g_isi_phonet_new(index);
450 if (inds == NULL || reqs == NULL) {
455 modem->req_fd = g_io_channel_unix_get_fd(reqs);
456 modem->req_watch = g_io_add_watch(reqs,
457 G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
458 isi_callback, modem);
459 modem->ind_fd = g_io_channel_unix_get_fd(inds);
460 modem->ind_watch = g_io_add_watch(inds,
461 G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
462 isi_callback, modem);
464 g_io_channel_unref(reqs);
465 g_io_channel_unref(inds);
467 modem->index = index;
468 modem->services = g_hash_table_new_full(g_direct_hash, NULL,
469 NULL, service_finalize);
474 GIsiModem *g_isi_modem_create_by_name(const char *name)
476 return g_isi_modem_create(if_nametoindex(name));
479 void *g_isi_modem_set_userdata(GIsiModem *modem, void *data)
481 void *old = modem->opaque;
483 modem->opaque = data;
487 void *g_isi_modem_get_userdata(GIsiModem *modem)
489 return modem->opaque;
492 static uint8_t service_next_utid(GIsiServiceMux *mux)
494 if (mux->last_utid == 0x00 || mux->last_utid == 0xFF)
497 return mux->last_utid + 1;
500 static void service_subs_incr(GIsiServiceMux *mux)
502 GIsiModem *modem = mux->modem;
504 mux->subscriptions++;
506 if (mux->subscriptions == 1)
507 modem_subs_update_when_idle(modem);
510 static void service_subs_decr(GIsiServiceMux *mux)
512 GIsiModem *modem = mux->modem;
514 if (mux->subscriptions == 0)
517 mux->subscriptions--;
519 if (mux->subscriptions == 0)
520 modem_subs_update_when_idle(modem);
523 static void service_regs_incr(GIsiServiceMux *mux)
525 mux->registrations++;
527 if (mux->registrations == 1)
528 service_name_register(mux);
531 static void service_regs_decr(GIsiServiceMux *mux)
533 if (mux->registrations == 0)
536 mux->registrations--;
538 if (mux->registrations == 0)
539 service_name_deregister(mux);
542 void g_isi_modem_destroy(GIsiModem *modem)
547 g_hash_table_remove_all(modem->services);
549 if (modem->subs_source > 0) {
550 g_source_remove(modem->subs_source);
551 modem_subs_update(modem);
554 g_hash_table_unref(modem->services);
556 if (modem->ind_watch > 0)
557 g_source_remove(modem->ind_watch);
559 if (modem->req_watch > 0)
560 g_source_remove(modem->req_watch);
565 unsigned g_isi_modem_index(GIsiModem *modem)
567 return modem != NULL ? modem->index : 0;
570 GIsiPending *g_isi_request_send(GIsiModem *modem, uint8_t resource,
571 const void *__restrict buf, size_t len,
572 unsigned timeout, GIsiNotifyFunc notify,
573 void *data, GDestroyNotify destroy)
575 struct sockaddr_pn dst = {
576 .spn_family = AF_PHONET,
577 .spn_resource = resource,
580 return g_isi_request_sendto(modem, &dst, buf, len, timeout, notify,
584 GIsiPending *g_isi_request_vsend(GIsiModem *modem, uint8_t resource,
585 const struct iovec *__restrict iov,
586 size_t iovlen, unsigned timeout,
587 GIsiNotifyFunc notify, void *data,
588 GDestroyNotify destroy)
590 struct sockaddr_pn dst = {
591 .spn_family = AF_PHONET,
592 .spn_resource = resource,
595 return g_isi_request_vsendto(modem, &dst, iov, iovlen, timeout, notify,
599 GIsiPending *g_isi_request_sendto(GIsiModem *modem, struct sockaddr_pn *dst,
600 const void *__restrict buf, size_t len,
601 unsigned timeout, GIsiNotifyFunc notify,
602 void *data, GDestroyNotify destroy)
604 const struct iovec iov = {
605 .iov_base = (void *)buf,
609 return g_isi_request_vsendto(modem, dst, &iov, 1, timeout, notify, data,
613 static void vtrace(struct sockaddr_pn *dst,
614 const struct iovec *__restrict iov, size_t iovlen,
615 size_t total_len, GIsiNotifyFunc trace)
617 uint8_t buffer[total_len];
618 uint8_t *ptr = buffer;
621 .data = (const void *)buffer,
626 for (i = 0; i < iovlen; i++) {
627 memcpy(ptr, iov[i].iov_base, iov[i].iov_len);
628 ptr += iov[i].iov_len;
634 static gboolean resp_timeout(gpointer data)
636 GIsiPending *resp = data;
642 pending_dispatch(resp, &msg);
645 g_isi_pending_remove(resp);
649 GIsiPending *g_isi_request_vsendto(GIsiModem *modem, struct sockaddr_pn *dst,
650 const struct iovec *__restrict iov,
651 size_t iovlen, unsigned timeout,
652 GIsiNotifyFunc notify, void *data,
653 GDestroyNotify destroy)
655 struct iovec _iov[1 + iovlen];
656 struct msghdr msg = {
657 .msg_name = (void *)dst,
658 .msg_namelen = sizeof(struct sockaddr_pn),
660 .msg_iovlen = 1 + iovlen,
676 mux = service_get(modem, dst->spn_resource);
682 resp = g_try_new0(GIsiPending, 1);
688 resp->type = GISI_MESSAGE_TYPE_RESP;
689 resp->utid = service_next_utid(mux);
691 resp->notify = notify;
692 resp->destroy = destroy;
695 if (g_slist_find_custom(mux->pending, resp, utid_equal)) {
697 * FIXME: perhaps retry with randomized access after
698 * initial miss. Although if the rate at which
699 * requests are sent is so high that the unique
700 * transaction ID wraps, it's likely there is
701 * something wrong and we might as well fail here.
703 ISIDBG(modem, "ERROR: UTID wrapped, modem busy");
708 _iov[0].iov_base = &resp->utid;
711 for (i = 0, len = 1; i < iovlen; i++) {
712 _iov[1 + i] = iov[i];
713 len += iov[i].iov_len;
716 if (modem->trace != NULL)
717 vtrace(dst, _iov, 1 + iovlen, len, modem->trace);
719 ret = sendmsg(modem->req_fd, &msg, MSG_NOSIGNAL);
723 if (ret != (ssize_t)len) {
728 mux->pending = g_slist_prepend(mux->pending, resp);
731 resp->timeout = g_timeout_add_seconds(timeout, resp_timeout,
734 mux->last_utid = resp->utid;
742 uint8_t g_isi_request_utid(GIsiPending *resp)
744 return resp != NULL ? resp->utid : 0;
747 GIsiPending *g_isi_pending_from_msg(const GIsiMessage *msg)
749 return msg != NULL ? msg->private : NULL;
752 void g_isi_pending_remove(GIsiPending *op)
757 op->service->pending = g_slist_remove(op->service->pending, op);
759 if (op->type == GISI_MESSAGE_TYPE_IND)
760 service_subs_decr(op->service);
762 if (op->type == GISI_MESSAGE_TYPE_REQ)
763 service_regs_decr(op->service);
765 if (op->type == GISI_MESSAGE_TYPE_RESP && op->notify != NULL) {
770 op->notify(&msg, op->data);
774 pending_destroy(op, NULL);
777 GIsiPending *g_isi_ntf_subscribe(GIsiModem *modem, uint8_t resource,
778 uint8_t msgid, GIsiNotifyFunc notify,
779 void *data, GDestroyNotify destroy)
784 mux = service_get(modem, resource);
790 ntf = g_try_new0(GIsiPending, 1);
796 ntf->type = GISI_MESSAGE_TYPE_NTF;
798 ntf->notify = notify;
800 ntf->destroy = destroy;
803 mux->pending = g_slist_append(mux->pending, ntf);
805 ISIDBG(modem, "Subscribed to %s (%p) [res=0x%02X, id=0x%02X]",
806 pend_type_to_str(ntf->type), ntf, resource, msgid);
811 GIsiPending *g_isi_service_bind(GIsiModem *modem, uint8_t resource,
812 uint8_t msgid, GIsiNotifyFunc notify,
813 void *data, GDestroyNotify destroy)
818 mux = service_get(modem, resource);
824 srv = g_try_new0(GIsiPending, 1);
830 srv->type = GISI_MESSAGE_TYPE_REQ;
832 srv->notify = notify;
834 srv->destroy = destroy;
837 mux->pending = g_slist_append(mux->pending, srv);
839 ISIDBG(modem, "Bound service for %s (%p) [res=0x%02X, id=0x%02X]",
840 pend_type_to_str(srv->type), srv, resource, msgid);
842 service_regs_incr(mux);
847 GIsiPending *g_isi_ind_subscribe(GIsiModem *modem, uint8_t resource,
848 uint8_t msgid, GIsiNotifyFunc notify,
849 void *data, GDestroyNotify destroy)
854 mux = service_get(modem, resource);
860 ind = g_try_new0(GIsiPending, 1);
866 ind->type = GISI_MESSAGE_TYPE_IND;
868 ind->notify = notify;
870 ind->destroy = destroy;
873 mux->pending = g_slist_append(mux->pending, ind);
875 ISIDBG(modem, "Subscribed for %s (%p) [res=0x%02X, id=0x%02X]",
876 pend_type_to_str(ind->type), ind, resource, msgid);
878 service_subs_incr(mux);
883 int g_isi_response_send(GIsiModem *modem, const GIsiMessage *req,
884 const void *__restrict buf, size_t len)
886 const struct iovec iov = {
887 .iov_base = (void *)buf,
891 return g_isi_response_vsend(modem, req, &iov, 1);
894 int g_isi_response_vsend(GIsiModem *modem, const GIsiMessage *req,
895 const struct iovec *__restrict iov,
898 struct iovec _iov[1 + iovlen];
902 utid = g_isi_msg_utid(req);
904 _iov[0].iov_base = &utid;
907 for (i = 0; i < iovlen; i++)
908 _iov[1 + i] = iov[i];
910 return g_isi_modem_vsendto(modem, req->addr, _iov, 1 + iovlen);
913 int g_isi_modem_sendto(GIsiModem *modem, struct sockaddr_pn *dst,
914 const void *__restrict buf, size_t len)
916 const struct iovec iov = {
917 .iov_base = (void *)buf,
921 return g_isi_modem_vsendto(modem, dst, &iov, 1);
924 int g_isi_modem_vsendto(GIsiModem *modem, struct sockaddr_pn *dst,
925 const struct iovec *__restrict iov,
928 struct msghdr msg = {
929 .msg_name = (void *)dst,
930 .msg_namelen = sizeof(struct sockaddr_pn),
931 .msg_iov = (struct iovec *)iov,
932 .msg_iovlen = iovlen,
944 mux = service_get(modem, dst->spn_resource);
948 for (i = 0, len = 0; i < iovlen; i++)
949 len += iov[i].iov_len;
951 if (modem->trace != NULL)
952 vtrace(dst, iov, iovlen, len, modem->trace);
954 ret = sendmsg(modem->req_fd, &msg, MSG_NOSIGNAL);
958 if (ret != (ssize_t)len)
964 void g_isi_modem_set_trace(GIsiModem *modem, GIsiNotifyFunc trace)
969 modem->trace = trace;
972 void g_isi_modem_set_debug(GIsiModem *modem, GIsiDebugFunc debug)
977 modem->debug = debug;
980 static int version_get_send(GIsiModem *modem, GIsiPending *ping)
982 GIsiServiceMux *mux = ping->service;
983 const struct sockaddr_pn dst = {
984 .spn_family = AF_PHONET,
985 .spn_resource = mux->resource,
988 ping->utid, /* UTID */
990 COMM_ISI_VERSION_GET_REQ,
995 if (g_slist_find_custom(mux->pending, ping, utid_equal))
998 ret = sendto(modem->req_fd, msg, sizeof(msg), MSG_NOSIGNAL,
999 (void *)&dst, sizeof(dst));
1004 if (ret != (ssize_t)sizeof(msg))
1007 mux->last_utid = ping->utid;
1008 mux->version_pending = TRUE;
1012 static gboolean reachable_notify(gpointer data)
1014 GIsiPending *pong = data;
1015 GIsiServiceMux *mux = pong->service;
1017 struct sockaddr_pn addr = {
1018 .spn_resource = mux->resource,
1019 .spn_dev = mux->object >> 8,
1020 .spn_obj = mux->object & 0xff,
1023 .version = &mux->version,
1028 pending_dispatch(pong, &msg);
1029 pong->notify = NULL;
1031 g_isi_pending_remove(pong);
1035 GIsiPending *g_isi_resource_ping(GIsiModem *modem, uint8_t resource,
1036 GIsiNotifyFunc notify, void *data,
1037 GDestroyNotify destroy)
1039 GIsiServiceMux *mux;
1043 mux = service_get(modem, resource);
1049 ping = g_try_new0(GIsiPending, 1);
1055 ping->type = GISI_MESSAGE_TYPE_COMMON;
1056 ping->utid = service_next_utid(mux);
1057 ping->service = mux;
1058 ping->notify = notify;
1060 ping->destroy = destroy;
1061 ping->msgid = COMM_ISI_VERSION_GET_REQ;
1063 if (mux->reachable) {
1064 g_idle_add(reachable_notify, ping);
1068 if (!mux->version_pending) {
1069 ret = version_get_send(modem, ping);
1075 mux->last_utid = ping->utid;
1078 ping->timeout = g_timeout_add_seconds(COMMON_TIMEOUT, resp_timeout,
1080 mux->pending = g_slist_prepend(mux->pending, ping);
1081 mux->version_pending = TRUE;
1083 ISIDBG(modem, "Ping sent %s (%p) [res=0x%02X]",
1084 pend_type_to_str(ping->type), ping, resource);