From 1584a74a4acdfb17aab0d6a2b13b18a7799aa37d Mon Sep 17 00:00:00 2001 From: laforge Date: Sat, 2 Sep 2006 10:32:06 +0000 Subject: first compiling (unfinished, not-working) version of userspace gsm infrastructure git-svn-id: http://svn.openmoko.org/trunk/src/target/gsm@40 99fdad57-331a-0410-800a-d7fa5415bdb3 --- src/gsmd/.gpsd.h.swp | Bin 0 -> 12288 bytes src/gsmd/Makefile | 14 ++ src/gsmd/atcmd.c | 332 ++++++++++++++++++++++++++++++++++++++++ src/gsmd/atcmd.h | 12 ++ src/gsmd/gsmd.c | 171 +++++++++++++++++++++ src/gsmd/gsmd.h | 68 ++++++++ src/gsmd/gsmd_event.h | 53 +++++++ src/gsmd/select.c | 96 ++++++++++++ src/gsmd/select.h | 22 +++ src/gsmd/unsolicited.c | 272 ++++++++++++++++++++++++++++++++ src/gsmd/unsolicited.h | 8 + src/gsmd/usock.c | 166 ++++++++++++++++++++ src/gsmd/usock.h | 15 ++ src/gsmd/vendor_ti.c | 53 +++++++ src/gsmd/vendorplugin.h | 29 ++++ src/libgsmd/lgsm_internals.h | 4 + src/libgsmd/libgsmd.c | 69 +++++++++ src/libgsmd/libgsmd_input.c | 13 ++ src/libgsmd/libgsmd_voicecall.c | 24 +++ 19 files changed, 1421 insertions(+) create mode 100644 src/gsmd/.gpsd.h.swp create mode 100644 src/gsmd/Makefile create mode 100644 src/gsmd/atcmd.c create mode 100644 src/gsmd/atcmd.h create mode 100644 src/gsmd/gsmd.c create mode 100644 src/gsmd/gsmd.h create mode 100644 src/gsmd/gsmd_event.h create mode 100644 src/gsmd/select.c create mode 100644 src/gsmd/select.h create mode 100644 src/gsmd/unsolicited.c create mode 100644 src/gsmd/unsolicited.h create mode 100644 src/gsmd/usock.c create mode 100644 src/gsmd/usock.h create mode 100644 src/gsmd/vendor_ti.c create mode 100644 src/gsmd/vendorplugin.h create mode 100644 src/libgsmd/lgsm_internals.h create mode 100644 src/libgsmd/libgsmd.c create mode 100644 src/libgsmd/libgsmd_input.c create mode 100644 src/libgsmd/libgsmd_voicecall.c (limited to 'src') diff --git a/src/gsmd/.gpsd.h.swp b/src/gsmd/.gpsd.h.swp new file mode 100644 index 0000000..75dfa6b Binary files /dev/null and b/src/gsmd/.gpsd.h.swp differ diff --git a/src/gsmd/Makefile b/src/gsmd/Makefile new file mode 100644 index 0000000..ab0a1bf --- /dev/null +++ b/src/gsmd/Makefile @@ -0,0 +1,14 @@ + +CFLAGS:=-I../../include -Wall -g +gsmd_OBJS:=gsmd.o atcmd.o select.o vendor_ti.o usock.o unsolicited.o + +all: gsmd + +gsmd: $(gsmd_OBJS) + $(CC) -o $@ $^ + +%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $^ + +clean: + rm -f $(gsmd_OBJS) gsmd diff --git a/src/gsmd/atcmd.c b/src/gsmd/atcmd.c new file mode 100644 index 0000000..f482eb2 --- /dev/null +++ b/src/gsmd/atcmd.c @@ -0,0 +1,332 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include "gsmd.h" +#include "atcmd.h" +#include "unsolicited.h" + +/* libgsmd / gsmd AT command interpreter / parser / constructor + * (C) 2006 by Harald Welte + * + * Written for First International Computer, Inc., Taiwan + */ + +enum final_result_codes { + GSMD_RESULT_OK = 0, + GSMD_RESULT_ERR = 1, + NUM_FINAL_RESULTS, +}; + +static const char *final_results[] = { + "OK", + "ERROR", + "+CME ERROR:" +}; + +/* we basically implement a parse that can deal with + * - receiving and queueing commands from higher level of libgmsd + * - optionally combining them into one larger command (; appending) + * - sending those commands to the TA, receiving and parsing responses + * - calling back application on completion, or waiting synchronously + * for a response + * - dealing with intermediate and unsolicited resultcodes by calling + * back into the application / higher levels + */ + +static inline int llparse_append(struct llparser *llp, char byte) +{ + if (llp->cur < llp->buf + llp->len) { + *(llp->cur++) = byte; + return 0; + } else + return -EFBIG; +} + +static int llparse_byte(struct llparser *llp, char byte) +{ + int ret = 0; + + switch (llp->state) { + case LLPARSE_STATE_IDLE: + if (llp->flags & LGSM_ATCMD_F_EXTENDED) { + if (byte == '\r') + llp->state = LLPARSE_STATE_IDLE_CR; + else { +#ifdef STRICT + llp->state = LLPARSE_STATE_ERROR; +#else + llp->state = LLPARSE_STATE_RESULT; + ret = llparse_append(llp, byte); +#endif + } + } else { + llp->state = LLPARSE_STATE_RESULT; + ret = llparse_append(llp, byte); + } + break; + case LLPARSE_STATE_IDLE_CR: + if (byte == '\n') + llp->state = LLPARSE_STATE_IDLE_LF; + else + llp->state = LLPARSE_STATE_ERROR; + break; + case LLPARSE_STATE_IDLE_LF: + /* can we really go directly into result_cr ? */ + if (byte == '\r') + llp->state = LLPARSE_STATE_RESULT_CR; + else { + llp->state = LLPARSE_STATE_RESULT; + ret = llparse_append(llp, byte); + } + break; + case LLPARSE_STATE_RESULT: + if (byte == '\r') + llp->state = LLPARSE_STATE_RESULT_CR; + else + ret = llparse_append(llp, byte); + break; + case LLPARSE_STATE_RESULT_CR: + if (byte == '\n') + llp->state = LLPARSE_STATE_IDLE; + break; + case LLPARSE_STATE_ERROR: + break; + } + + return ret; +} + +static int llparse_string(struct llparser *llp, char *buf, unsigned int len) +{ + while (len--) { + int rc = llparse_byte(llp, *(buf++)); + if (rc < 0) + return rc; + + /* if _after_ parsing the current byte we have finished, + * let the caller know that there is something to handle */ + if (llp->state == LLPARSE_STATE_RESULT_CR) { + llp->cb(llp->buf, llp->cur - llp->buf, llp->ctx); + /* re-set cursor to start of buffer */ + llp->cur = llp->buf; + } + } + + return 0; +} + +/* mid-level parser */ + +static int parse_final_result(const char *res) +{ + int i; + for (i = 0; i < NUM_FINAL_RESULTS; i++) { + if (!strcmp(res, final_results[i])) + return i; + } + + return -1; +} + +static int ml_parse(const char *buf, int len, void *ctx) +{ + struct gsmd *g = ctx; + struct gsmd_atcmd *cmd; + int final = 0; + + /* responses come in order, so first response has to be for first + * command we sent, i.e. first entry in list */ + cmd = llist_entry(g->busy_atcmds.next, struct gsmd_atcmd, list); + + if (buf[0] == '+') { + /* an extended response */ + const char *colon = strchr(buf, ':'); + if (!colon) { + fprintf(stderr, "no colon in extd response `%s'\n", + buf); + return -EINVAL; + } + if (cmd->buf[2] != '+') { + fprintf(stderr, "extd reply to non-extd command?\n"); + return -EINVAL; + } + + if (!strncmp(buf+1, "CME ERROR", 9)) { + unsigned long err_nr; + err_nr = strtoul(colon+1, NULL, 10); + DEBUGP("error number %lu\n", err_nr); + cmd->ret = err_nr; + final = 1; + goto final_cb; + } + + if (strncmp(buf, &cmd->buf[2], colon-buf)) { + DEBUGP("extd reply `%s' to cmd `%s', must be " + "unsolicited\n", buf, &cmd->buf[2]); + colon++; + if (colon > buf+len) + colon = NULL; + return unsolicited_parse(g, buf, len, colon); + } + + /* if we survive till here, it's a valid extd response + * to an extended command */ + + /* FIXME: solve multi-line responses ! */ + if (cmd->buflen < len) + len = cmd->buflen; + + memcpy(cmd->buf, buf, len); + } else { + + /* this is the only non-extended unsolicited return code */ + if (!strcmp(buf, "RING")) + return unsolicited_parse(g, buf, len, NULL); + + if (!strcmp(buf, "ERROR") || + ((g->flags & GSMD_FLAG_V0) && cmd->buf[0] == '4')){ + DEBUGP("unspecified error\n"); + cmd->ret = 4; + final = 1; + goto final_cb; + } + + if (!strncmp(buf, "OK", 2) + || ((g->flags & GSMD_FLAG_V0) && cmd->buf[0] == '0')) { + cmd->ret = 0; + final = 1; + goto final_cb; + } + + /* FIXME: handling of those special commands in response to + * ATD / ATA */ + if (!strncmp(buf, "NO CARRIER", 10)) { + } + + if (!strncmp(buf, "BUSY", 4)) { + } + } + +final_cb: + if (final) { + /* remove from list of currently executing cmds */ + llist_del(&cmd->list); + + if (cmd->cb) { + fprintf(stderr, "command without cb!!!\n"); + return -EINVAL; + } + return cmd->cb(cmd, cmd->ctx); + + } + + return 0; +} + +/* callback to be called if [virtual] UART has some data for us */ +static int atcmd_select_cb(int fd, unsigned int what, void *data) +{ + int len; + static char rxbuf[1024]; + struct gsmd *g = data; + + if (what & GSMD_FD_READ) { + while ((len = read(fd, rxbuf, sizeof(rxbuf)))) { + int rc; + + if (len < 0) { + DEBUGP("ERROR reading from fd %u: %d (%s)\n", fd, len, + strerror(errno)); + return len; + } + rc = llparse_string(&g->llp, rxbuf, len); + if (rc < 0) { + DEBUGP("ERROR during llparse_string: %d\n", rc); + return rc; + } + } + } + + /* write pending commands to UART */ + if (what & GSMD_FD_WRITE) { + struct gsmd_atcmd *pos, *pos2; + llist_for_each_entry_safe(pos, pos2, &g->pending_atcmds, list) { + int rc = write(fd, pos->buf, strlen(pos->buf)); + if (rc == 0) { + DEBUGP("write returns 0, aborting\n"); + break; + } else if (rc < 0) { + DEBUGP("error during write to fd %d: %d\n", + fd, rc); + return rc; + } + if (rc < len) { + fprintf(stderr, "short write!!! FIXME!\n"); + exit(3); + } + /* success: remove from global list of to-be-sent atcmds */ + llist_del(&pos->list); + /* append to global list of executing atcmds */ + llist_add_tail(&pos->list, &g->busy_atcmds); + } + } + + return 0; +} + + +struct gsmd_atcmd *atcmd_fill(const char *cmd, int rlen, + atcmd_cb_t cb, void *ctx) +{ + int buflen = strlen(cmd); + struct gsmd_atcmd *atcmd; + + if (rlen > buflen) + buflen = rlen; + + atcmd = malloc(sizeof(*atcmd)+ buflen); + if (!atcmd) + return NULL; + + atcmd->ctx = ctx; + atcmd->flags = 0; + atcmd->ret = -255; + atcmd->buflen = buflen; + atcmd->buf[buflen-1] = '\0'; + atcmd->cb = cb; + strncpy(atcmd->buf, cmd, buflen-1); + + return atcmd; +} + +/* submit an atcmd in the global queue of pending atcmds */ +int atcmd_submit(struct gsmd *g, struct gsmd_atcmd *cmd) +{ + llist_add_tail(&cmd->list, &g->pending_atcmds); + g->gfd_uart.when |= GSMD_FD_WRITE; + + return 0; +} + +/* init atcmd parser */ +int atcmd_init(struct gsmd *g, int sockfd) +{ + g->gfd_uart.fd = sockfd; + g->gfd_uart.when = GSMD_FD_READ; + g->gfd_uart.data = g; + g->gfd_uart.cb = &atcmd_select_cb; + + g->llp.cur = g->llp.buf; + g->llp.cb = &ml_parse; + g->llp.ctx = g; + g->llp.flags = LGSM_ATCMD_F_EXTENDED; + + return gsmd_register_fd(&g->gfd_uart); +} + diff --git a/src/gsmd/atcmd.h b/src/gsmd/atcmd.h new file mode 100644 index 0000000..f6b9d1d --- /dev/null +++ b/src/gsmd/atcmd.h @@ -0,0 +1,12 @@ +#ifndef __GSMD_ATCMD_H +#define __GSMD_ATCMD_H + +#include "gsmd.h" + +typedef int atcmd_cb_t(struct gsmd_atcmd *cmd, void *ctx); + +struct gsmd_atcmd *atcmd_fill(const char *cmd, int rlen, atcmd_cb_t *cb, void *ctx); +int atcmd_submit(struct gsmd *g, struct gsmd_atcmd *cmd); +int atcmd_init(struct gsmd *g, int sockfd); + +#endif diff --git a/src/gsmd/gsmd.c b/src/gsmd/gsmd.c new file mode 100644 index 0000000..ea44a73 --- /dev/null +++ b/src/gsmd/gsmd.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include +#include + +#include + +#include "gsmd.h" +#include "atcmd.h" +#include "select.h" +#include "usock.h" +#include "vendorplugin.h" + +static int gsmd_test_atcb(struct gsmd_atcmd *cmd, void *ctx) +{ + printf("returned: `%s'\n", cmd->buf); + free(cmd); + return 0; +} + +static int gsmd_test(struct gsmd *gsmd) +{ + struct gsmd_atcmd *cmd; + cmd = atcmd_fill("AT+CLCK=?", 255, &gsmd_test_atcb, NULL); + return atcmd_submit(gsmd, cmd); +} + +struct bdrt { + int bps; + u_int32_t b; +}; + +static struct bdrt bdrts[] = { + { 0, B0 }, + { 9600, B9600 }, + { 19200, B19200 }, + { 38400, B38400 }, + { 57600, B57600 }, + { 115200, B115200 }, +}; + +static int set_baudrate(int fd, int baudrate) +{ + int i; + u_int32_t bd = 0; + struct termios ti; + + for (i = 0; i < ARRAY_SIZE(bdrts); i++) { + if (bdrts[i].bps == baudrate) + bd = bdrts[i].b; + } + if (bd == 0) + return -EINVAL; + + i = tcgetattr(fd, &ti); + if (i < 0) + return i; + + i = cfsetispeed(&ti, B0); + if (i < 0) + return i; + + i = cfsetospeed(&ti, bd); + if (i < 0) + return i; + + return tcsetattr(fd, 0, &ti); +} + + +static struct gsmd g; + +static struct option opts[] = { + { "version", 0, NULL, 'V' }, + { "daemon", 0, NULL, 'd' }, + { "help", 0, NULL, 'h' }, + { "device", 1, NULL, 'p' }, + { "speed", 1, NULL, 's' }, +}; + +int main(int argc, char **argv) +{ + int fd, argch; + + int daemonize = 0; + int bps = 115200; + char *device = "/dev/ttyUSB0"; + + /*FIXME: parse commandline, set daemonize, device, ... */ + while ((argch = getopt_long(argc, argv, "Vdhps:", opts, NULL)) != -1) { + switch (argch) { + case 'V': + /* FIXME */ + break; + case 'd': + daemonize = 1; + break; + case 'h': + /* FIXME */ + break; + case 'p': + device = optarg; + break; + case 's': + bps = atoi(optarg); + break; + } + } + + /* use direct access to device node ([virtual] tty device) */ + fd = open(device, O_RDWR); + if (fd < 0) { + fprintf(stderr, "can't open device `%s': %s\n", + device, strerror(errno)); + exit(1); + } + + if (set_baudrate(fd, bps) < 0) { + fprintf(stderr, "can't set baudrate\n"); + exit(1); + } + + if (atcmd_init(&g, fd) < 0) { + fprintf(stderr, "can't initialize UART device\n"); + exit(1); + } + + if (usock_init(&g) < 0) { + fprintf(stderr, "can't open unix socket\n"); + exit(1); + } + + if (daemonize) { + if (fork()) { + exit(0); + } + fclose(stdout); + fclose(stderr); + fclose(stdin); + setsid(); + } + + gsmd_test(&g); + + while (1) { + int ret = gsmd_select_main(); + if (ret == 0) + continue; + + if (ret < 0) { + if (errno == -EINTR) + continue; + else { + DEBUGP("select returned error (%s)\n", + strerror(errno)); + break; + } + } + } + + exit(0); +} diff --git a/src/gsmd/gsmd.h b/src/gsmd/gsmd.h new file mode 100644 index 0000000..ff05a27 --- /dev/null +++ b/src/gsmd/gsmd.h @@ -0,0 +1,68 @@ +#ifndef __GSMD_H +#define __GSMD_H + +#include + +#include + +#include "select.h" + +/* Refer to 3GPP TS 07.07 v 7.8.0, Chapter 4.1 */ +#define LGSM_ATCMD_F_EXTENDED 0x01 /* as opposed to basic */ +#define LGSM_ATCMD_F_PARAM 0x02 /* as opposed to action */ + +struct gsmd_atcmd { + struct llist_head list; + void *ctx; + int (*cb)(struct gsmd_atcmd *, void *); + u_int8_t flags; + int32_t ret; + u_int32_t buflen; + char buf[]; +}; + +enum llparse_state { + LLPARSE_STATE_IDLE, /* idle, not parsing a response */ + LLPARSE_STATE_IDLE_CR, /* CR before response (V1) */ + LLPARSE_STATE_IDLE_LF, /* LF before response (V1) */ + LLPARSE_STATE_RESULT, /* within result payload */ + LLPARSE_STATE_RESULT_CR, /* CR after result */ + LLPARSE_STATE_ERROR, /* something went wrong */ + /* ... idle again */ +}; + +/* we can't take any _single_ response bigger than this: */ +#define LLPARSE_BUF_SIZE 256 + +struct llparser { + enum llparse_state state; + unsigned int len; + unsigned int flags; + void *ctx; + int (*cb)(const char *buf, int len, void *ctx); + char *cur; + char buf[LLPARSE_BUF_SIZE]; +}; + +#define GSMD_FLAG_V0 0x0001 /* V0 responses to be expected from TA */ + +struct gsmd { + unsigned int flags; + struct gsmd_fd gfd_uart; + struct gsmd_fd gfd_sock; + struct llparser llp; + struct llist_head users; + struct llist_head pending_atcmds; /* our busy gsmd_atcmds */ + struct llist_head busy_atcmds; /* our busy gsmd_atcmds */ +}; + +struct gsmd_user { + struct llist_head list; /* our entry in the global list */ + struct llist_head finished_ucmds; /* our busy gsmd_ucmds */ + struct gsmd *gsmd; + struct gsmd_fd gfd; /* the socket */ + u_int32_t subscriptions; /* bitmaks of subscribed event groups */ +}; + +#define DEBUGP(x, args ...) printf("%s:%s(%d):" x, __FILE__, __FUNCTION__, __LINE__, ## args) +#endif diff --git a/src/gsmd/gsmd_event.h b/src/gsmd/gsmd_event.h new file mode 100644 index 0000000..b25ee9d --- /dev/null +++ b/src/gsmd/gsmd_event.h @@ -0,0 +1,53 @@ +#ifndef __GSMD_EVENT_H +#define __GSMD_EVENT_H + +/* event handling */ + +enum gsmd_event_type { + GSMD_EVTTYPE_NONE, + GSMD_EVTTYPE_VOICECALL, + GSMD_EVTTYPE_DATACALL, + GSMD_EVTTYPE_SMS, + GSMD_EVTTYPE_GPRS, + GSMD_EVTTYPE_CIPHER_IND, +}; + +enum gsmd_event_call { + GSMD_EVT_CALL_NONE, + GSMD_EVT_CALL_HANGUP, /* any call: hanged up */ + GSMD_EVT_CALL_RING, /* incoming call: we're ringing */ + GSMD_EVT_CALL_BUSY, /* outgoing call: busy */ + GSMD_EVT_CALL_RINGING, /* outgoing call: other end ringing */ + GSMD_EVT_CALL_ESTABLISHED, /* any call: now established */ +}; + +enum gsmd_event_voice { + /* all of event_call */ +}; + +enum gsmd_event_data { + /* all of event_call */ +}; + +enum gsmd_event_sms { + GSMD_EVT_SMS_NONE, + GSMD_EVT_SMS_RCVD, /* incoming SMS received */ + GSMD_EVT_SMS_OVERFLOW, /* sms memory full, can't receive */ +}; + +enum gsmd_event_gprs { +}; + +enum gsmd_event_cipher { + GSMD_EVT_CI_NONE, + GSMD_EVT_CI_ENABLED, /* cipher enabled */ + GSMD_EVT_CI_DISABLED, /* cipher disabled */ +}; + +enum gsmd_event_network { + GSMD_EVT_NW_NONE, + GSMD_EVT_NW_SIGNAL, /* signal strength */ + GSMD_EVT_NW_REG, /* network registration */ +}; + +#endif diff --git a/src/gsmd/select.c b/src/gsmd/select.c new file mode 100644 index 0000000..62dc4c2 --- /dev/null +++ b/src/gsmd/select.c @@ -0,0 +1,96 @@ +/* select() FD handling for GSM Daemon. + * (C) 2000-2006 by Harald Welte + * + * Based on code originally written by myself for ulogd. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include "select.h" + +static int maxfd = 0; +static LLIST_HEAD(gsmd_fds); + +int gsmd_register_fd(struct gsmd_fd *fd) +{ + int flags; + + /* make FD nonblocking */ + flags = fcntl(fd->fd, F_GETFL); + if (flags < 0) + return -1; + flags |= O_NONBLOCK; + flags = fcntl(fd->fd, F_SETFL, flags); + if (flags < 0) + return -1; + + /* Register FD */ + if (fd->fd > maxfd) + maxfd = fd->fd; + + llist_add_tail(&fd->list, &gsmd_fds); + + return 0; +} + +void gsmd_unregister_fd(struct gsmd_fd *fd) +{ + llist_del(&fd->list); +} + +int gsmd_select_main() +{ + struct gsmd_fd *ufd; + fd_set readset, writeset, exceptset; + int i; + + FD_ZERO(&readset); + FD_ZERO(&writeset); + FD_ZERO(&exceptset); + + /* prepare read and write fdsets */ + llist_for_each_entry(ufd, &gsmd_fds, list) { + if (ufd->when & GSMD_FD_READ) + FD_SET(ufd->fd, &readset); + + if (ufd->when & GSMD_FD_WRITE) + FD_SET(ufd->fd, &writeset); + + if (ufd->when & GSMD_FD_EXCEPT) + FD_SET(ufd->fd, &exceptset); + } + + i = select(maxfd+1, &readset, &writeset, &exceptset, NULL); + if (i > 0) { + /* call registered callback functions */ + llist_for_each_entry(ufd, &gsmd_fds, list) { + int flags = 0; + + if (FD_ISSET(ufd->fd, &readset)) + flags |= GSMD_FD_READ; + + if (FD_ISSET(ufd->fd, &writeset)) + flags |= GSMD_FD_WRITE; + + if (FD_ISSET(ufd->fd, &exceptset)) + flags |= GSMD_FD_EXCEPT; + + if (flags) + ufd->cb(ufd->fd, flags, ufd->data); + } + } + return i; +} diff --git a/src/gsmd/select.h b/src/gsmd/select.h new file mode 100644 index 0000000..c734eb6 --- /dev/null +++ b/src/gsmd/select.h @@ -0,0 +1,22 @@ +#ifndef __GSMD_SELECT_H +#define __GSMD_SELECT_H + +#include + +#define GSMD_FD_READ 0x0001 +#define GSMD_FD_WRITE 0x0002 +#define GSMD_FD_EXCEPT 0x0004 + +struct gsmd_fd { + struct llist_head list; + int fd; /* file descriptor */ + unsigned int when; + int (*cb)(int fd, unsigned int what, void *data); + void *data; /* void * to pass to callback */ +}; + +int gsmd_register_fd(struct gsmd_fd *ufd); +void gsmd_unregister_fd(struct gsmd_fd *ufd); +int gsmd_select_main(void); + +#endif diff --git a/src/gsmd/unsolicited.c b/src/gsmd/unsolicited.c new file mode 100644 index 0000000..fdcdac7 --- /dev/null +++ b/src/gsmd/unsolicited.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gsmd.h" +#include "usock.h" + +static struct gsmd_ucmd *build_event(u_int8_t type, u_int8_t subtype, u_int8_t len) +{ + struct gsmd_ucmd *ucmd = malloc(sizeof(*ucmd)+len); + + if (!ucmd) + return NULL; + + ucmd->hdr.version = GSMD_PROTO_VERSION; + ucmd->hdr.msg_type = type; + ucmd->hdr.msg_subtype = subtype; + ucmd->hdr.len = len; + + return ucmd; +} + +static struct gsmd_ucmd *ucmd_copy(const struct gsmd_ucmd *orig) +{ + int size = sizeof(*orig) + orig->hdr.len; + struct gsmd_ucmd *copy = malloc(size); + + if (copy) + memcpy(copy, orig, size); + + return copy; +} + +static int usock_evt_send(struct gsmd *gsmd, struct gsmd_ucmd *ucmd, u_int32_t evt) +{ + struct gsmd_user *gu; + int num_sent = 0; + + llist_for_each_entry(gu, &gsmd->users, list) { + if (gu->subscriptions & (1 << evt)) { + struct gsmd_ucmd *cpy = ucmd_copy(ucmd); + llist_add_tail(&ucmd->list, &gu->finished_ucmds); + num_sent++; + ucmd = cpy; + if (!ucmd) { + fprintf(stderr, "can't allocate memory for copy of ucmd\n"); + return num_sent; + } + } + } + + return num_sent; +} + +static int ring_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + /* FIXME: generate ring event */ + struct gsmd_ucmd *ucmd = build_event(GSMD_MSG_EVENT, GSMD_EVT_IN_CALL, + sizeof(struct gsmd_evt_auxdata)); + struct gsmd_evt_auxdata *aux; + if (!ucmd) + return -ENOMEM; + + aux = (struct gsmd_evt_auxdata *)ucmd->buf; + + aux->u.call.type = GSMD_CALL_UNSPEC; + + return usock_evt_send(gsmd, ucmd, GSMD_EVT_IN_CALL); +} + +static int cring_parse(const char *buf, int len, const char *param, struct gsmd *gsmd) +{ + struct gsmd_ucmd *ucmd = build_event(GSMD_MSG_EVENT, GSMD_EVT_IN_CALL, + sizeof(struct gsmd_evt_auxdata)); + struct gsmd_evt_auxdata *aux; + if (!ucmd) + return -ENOMEM; + + aux = (struct gsmd_evt_auxdata *) ucmd->buf; + + if (!strcmp(param, "VOICE")) { + /* incoming voice call */ + aux->u.call.type = GSMD_CALL_VOICE; + } else if (!strcmp(param, "SYNC")) { + aux->u.call.type = GSMD_CALL_DATA_SYNC; + } else if (!strcmp(param, "REL ASYNC")) { + aux->u.call.type = GSMD_CALL_DATA_REL_ASYNC; + } else if (!strcmp(param, "REL SYNC")) { + aux->u.call.type = GSMD_CALL_DATA_REL_SYNC; + } else if (!strcmp(param, "FAX")) { + aux->u.call.type = GSMD_CALL_FAX; + } else if (!strncmp(param, "GPRS ", 5)) { + /* FIXME: change event type to GPRS */ + free(ucmd); + return 0; + } + /* FIXME: parse all the ALT* profiles, Chapter 6.11 */ + + return usock_evt_send(gsmd, ucmd, GSMD_EVT_IN_CALL); +} + +/* Chapter 7.2, network registration */ +static int creg_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + const char *comma = strchr(param, ','); + struct gsmd_ucmd *ucmd = build_event(GSMD_MSG_EVENT, GSMD_EVT_NETREG, + sizeof(struct gsmd_evt_auxdata)); + struct gsmd_evt_auxdata *aux; + if (!ucmd) + return -ENOMEM; + aux = (struct gsmd_evt_auxdata *) ucmd->buf; + + aux->u.netreg.state = atoi(param); + if (comma) { + /* FIXME: we also have location area code and cell id to parse (hex) */ + } + + return usock_evt_send(gsmd, ucmd, GSMD_EVT_NETREG); +} + +/* Chapter 7.11, call waiting */ +static int ccwa_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + const char *number; + u_int8_t type, class; + + /* FIXME: parse */ + return 0; +} + +/* Chapter 7.14, unstructured supplementary service data */ +static int cusd_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + /* FIXME: parse */ + return 0; +} + +/* Chapter 7.15, advise of charge */ +static int cccm_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + /* FIXME: parse */ + return 0; +} + +/* Chapter 10.1.13, GPRS event reporting */ +static int cgev_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + /* FIXME: parse */ + return 0; +} + +/* Chapter 10.1.14, GPRS network registration status */ +static int cgreg_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + /* FIXME: parse */ + return 0; +} + +/* Chapter 7.6, calling line identification presentation */ +static int clip_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + struct gsmd_ucmd *ucmd = build_event(GSMD_MSG_EVENT, GSMD_EVT_IN_CLIP, + sizeof(struct gsmd_evt_auxdata)); + struct gsmd_evt_auxdata *aux; + const char *comma = strchr(param, ','); + + if (!ucmd) + return -ENOMEM; + + aux = (struct gsmd_evt_auxdata *) ucmd->buf; + + if (!comma) + return -EINVAL; + + if (comma - param > GSMD_ADDR_MAXLEN) + return -EINVAL; + + memcpy(aux->u.clip.addr.number, param, comma-param); + /* FIXME: parse of subaddr, etc. */ + + return usock_evt_send(gsmd, ucmd, GSMD_EVT_IN_CLIP); +} + +/* Chapter 7.9, calling line identification presentation */ +static int colp_parse(const char *buf, int len, const char *param, + struct gsmd *gsmd) +{ + struct gsmd_ucmd *ucmd = build_event(GSMD_MSG_EVENT, GSMD_EVT_OUT_COLP, + sizeof(struct gsmd_evt_auxdata)); + struct gsmd_evt_auxdata *aux; + const char *comma = strchr(param, ','); + + if (!ucmd) + return -ENOMEM; + + aux = (struct gsmd_evt_auxdata *) ucmd->buf; + + if (!comma) + return -EINVAL; + + if (comma - param > GSMD_ADDR_MAXLEN) + return -EINVAL; + + memcpy(aux->u.colp.addr.number, param, comma-param); + /* FIXME: parse of subaddr, etc. */ + + return usock_evt_send(gsmd, ucmd, GSMD_EVT_OUT_COLP); +} + +struct gsmd_unsolicit { + const char *prefix; + int (*parse)(const char *unsol, int len, const char *param, struct gsmd *gsmd); +}; + +static const struct gsmd_unsolicit gsm0707_unsolicit[] = { + { "RING", &ring_parse }, + { "+CRING", &cring_parse }, + { "+CREG", &creg_parse }, + { "+CCWA", &ccwa_parse }, + { "+CUSD", &cusd_parse }, + { "+CCCM", &cccm_parse }, + { "+CGEV", &cgev_parse }, + { "+CGREG", &cgreg_parse }, + { "+CLIP", &clip_parse }, + { "+COLP", &colp_parse }, + /* + { "+CKEV", &ckev_parse }, + { "+CDEV", &cdev_parse }, + { "+CIEV", &ciev_parse }, + { "+CLAV", &clav_parse }, + { "+CCWV", &ccwv_parse }, + { "+CLAV", &clav_parse }, + { "+CSSU", &cssu_parse }, + */ +}; + +/* called by midlevel parser if a response seems unsolicited */ +int unsolicited_parse(struct gsmd *g, const char *buf, int len, const char *param) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(gsm0707_unsolicit); i++) { + const char *colon; + if (strncmp(buf, gsm0707_unsolicit[i].prefix, + strlen(gsm0707_unsolicit[i].prefix))) + continue; + + colon = strchr(buf, ':') + 1; + if (colon > buf+len) + colon = NULL; + + return gsm0707_unsolicit[i].parse(buf, len, colon, g); + } + + /* FIXME: call vendor-specific unsolicited code parser */ + return -EINVAL; +} + diff --git a/src/gsmd/unsolicited.h b/src/gsmd/unsolicited.h new file mode 100644 index 0000000..4cc5bb5 --- /dev/null +++ b/src/gsmd/unsolicited.h @@ -0,0 +1,8 @@ +#ifndef __GSMD_UNSOLICITED_H +#define __GSMD_UNSOLICITED_H + +#include "gsmd.h" + +int unsolicited_parse(struct gsmd *g, const char *buf, int len, const char *param); + +#endif diff --git a/src/gsmd/usock.c b/src/gsmd/usock.c new file mode 100644 index 0000000..b00c031 --- /dev/null +++ b/src/gsmd/usock.c @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "gsmd.h" +#include "select.h" +#include "atcmd.h" +#include "usock.h" + +/* callback for completed passthrough gsmd_atcmd's */ +static int usock_cmd_cb(struct gsmd_atcmd *cmd, void *ctx) +{ + struct gsmd_user *gu = ctx; + struct gsmd_ucmd *ucmd = malloc(sizeof(*ucmd)+cmd->buflen); + + if (!ucmd) + return -ENOMEM; + + ucmd->hdr.version = GSMD_PROTO_VERSION; + ucmd->hdr.msg_type = GSMD_MSG_PASSTHROUGH; + ucmd->hdr.msg_subtype = GSMD_PASSTHROUGH_RESP; + ucmd->hdr.len = cmd->buflen; + memcpy(ucmd->buf, cmd->buf, ucmd->hdr.len); + + /* add to per-user list of finished cmds */ + llist_add_tail(&ucmd->list, &gu->finished_ucmds); + + /* mark socket of user as we-want-to-write */ + gu->gfd.when |= GSMD_FD_WRITE; + + return 0; +} + +static int usock_rcv_pcmd(struct gsmd_user *gu, char *buf, int len) +{ + struct gsmd_msg_hdr *gph = (struct gsmd_msg_hdr *)buf; + + if (gph->version != GSMD_PROTO_VERSION) + return -EINVAL; + + switch (gph->msg_type) { + case GSMD_MSG_PASSTHROUGH: + { + struct gsmd_atcmd *cmd; + cmd = atcmd_fill((char *)gph+sizeof(*gph), + 255, &usock_cmd_cb, gu); + if (!cmd) + return -ENOMEM; + return atcmd_submit(gu->gsmd, cmd); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/* callback for read/write on client (libgsmd) socket */ +static int gsmd_usock_user_cb(int fd, unsigned int what, void *data) +{ + struct gsmd_user *gu = data; + + /* FIXME: check some kind of backlog and limit it */ + + if (what & GSMD_FD_READ) { + char buf[1024]; + int rcvlen; + /* read data from socket, determine what he wants */ + rcvlen = read(fd, buf, sizeof(buf)); + return usock_rcv_pcmd(gu, buf, rcvlen); + } + + if (what & GSMD_FD_WRITE) { + /* write data from pending replies to socket */ + struct gsmd_ucmd *ucmd, *uctmp; + llist_for_each_entry_safe(ucmd, uctmp, &gu->finished_ucmds, + list) { + int rc; + + rc = write(fd, &ucmd->hdr, sizeof(ucmd->hdr) + ucmd->hdr.len); + if (rc < 0) { + DEBUGP("write return %d\n", rc); + return rc; + } + if (rc == 0) { + DEBUGP("write returns zero!!\n"); + break; + } + if (rc != sizeof(ucmd->hdr) + ucmd->hdr.len) { + DEBUGP("short write\n"); + break; + } + + llist_del(&ucmd->list); + free(ucmd); + } + } + + return 0; +} + +/* callback for read on master-listen-socket */ +static int gsmd_usock_cb(int fd, unsigned int what, void *data) +{ + struct gsmd *g = data; + struct gsmd_user *newuser; + + /* FIXME: implement this */ + if (what & GSMD_FD_READ) { + /* new incoming connection */ + newuser = malloc(sizeof(*newuser)); + if (!newuser) + return -ENOMEM; + + newuser->gfd.fd = accept(fd, NULL, 0); + if (newuser->gfd.fd < 0) { + DEBUGP("error accepting incoming conn: `%s'\n", + strerror(errno)); + free(newuser); + } + newuser->gfd.when = GSMD_FD_READ; + newuser->gfd.data = newuser; + newuser->gfd.cb = &gsmd_usock_user_cb; + newuser->gsmd = g; + + llist_add(&newuser->list, &g->users); + } + + return 0; +} + +/* handling of socket with incoming client connections */ +int usock_init(struct gsmd *g) +{ + struct sockaddr_un sun; + int fd, rc; + + fd = socket(PF_UNIX, GSMD_UNIX_SOCKET_TYPE, 0); + if (fd < 0) + return fd; + + sun.sun_family = AF_UNIX; + memcpy(sun.sun_path, GSMD_UNIX_SOCKET, sizeof(GSMD_UNIX_SOCKET)); + + rc = bind(fd, (struct sockaddr *)&sun, sizeof(sun)); + if (rc < 0) { + close(fd); + return rc; + } + + g->gfd_sock.fd = fd; + g->gfd_sock.when = GSMD_FD_READ | GSMD_FD_EXCEPT; + g->gfd_sock.data = g; + g->gfd_sock.cb = &gsmd_usock_cb; + + return gsmd_register_fd(&g->gfd_sock); +} diff --git a/src/gsmd/usock.h b/src/gsmd/usock.h new file mode 100644 index 0000000..aa7c43f --- /dev/null +++ b/src/gsmd/usock.h @@ -0,0 +1,15 @@ +#ifndef __GSMD_USOCK_H +#define __GSMD_USOCK_H + +#include + +int usock_init(struct gsmd *g); + +struct gsmd_ucmd { + struct llist_head list; + struct gsmd_msg_hdr hdr; + char buf[]; +} __attribute__ ((packed)); + +#endif + diff --git a/src/gsmd/vendor_ti.c b/src/gsmd/vendor_ti.c new file mode 100644 index 0000000..ac67b92 --- /dev/null +++ b/src/gsmd/vendor_ti.c @@ -0,0 +1,53 @@ +/* TI [Calypso] compatible backend */ + +#include +#include +#include +#include + +#include "vendorplugin.h" + +static int +ti_getopt(struct gsmd *gh, int optname, void *optval, int *optlen) +{ + switch (optname) { + case GSMD_OPT_CIPHER_IND: + /* FIXME: send AT%CPRI=? */ + break; + default: + return -EINVAL; + } +} + +static int +ti_setopt(struct gsmd *gh, int optname, const void *optval, int optlen) +{ + switch (optname) { + case GSMD_OPT_CIPHER_IND: + /* FIXME: send AT%CPRI= */ + break; + default: + return -EINVAL; + } +} + +static int ti_parseunsolicit(struct gsmd *gh) +{ + /* FIXME: parse all the below and generate the respective events */ + + /* %CPROAM: CPHS Home Country Roaming Indicator */ + /* %CPVWI: CPHS Voice Message Waiting */ + /* %CGREG: reports extended information about GPRS registration state */ + /* %CTZV: reports network time and date information */ + /* %CNIV: reports network name information */ + /* %CPKY: Press Key */ + /* %CMGRS: Message Retransmission Service */ + /* %CGEV: reports GPRS network events */ + return -EINVAL; +} + +struct gsmd_vendorspecific ti_vendorspec = { + .getopt = &ti_getopt, + .setopt = &ti_setopt, + .parse_unsolicit = &ti_parseunsolicit, +}; diff --git a/src/gsmd/vendorplugin.h b/src/gsmd/vendorplugin.h new file mode 100644 index 0000000..b8be9ba --- /dev/null +++ b/src/gsmd/vendorplugin.h @@ -0,0 +1,29 @@ +#ifndef __GSMD_VENDORPLUG_H +#define __GSMD_VENDORPLUG_H + +#include "gsmd.h" + +/* gsmd vendor-specific plugin */ + +enum gsmd_options { + GSMD_OPT_NONE, + GSMD_OPT_CIPHER_IND, +}; + +/* CIPHER_IND */ +enum gsmd_cipher_ind { + GSMD_CIPHER_IND_OFF, + GSMD_CIPHER_IND_ON, + GSMD_CIPHER_IND_SIM_FORBID, +}; + +struct gsmd_vendorspecific { + /* callback function to parse unknown unsolicited responses */ + int (*parse_unsolicit)(void); + int (*getopt)(struct gsmd *gh, int optname, void *optval, int *optlen); + int (*setopt)(struct gsmd *gh, int optname, const void *optval, int optlen); +}; + +/* ciphering indications */ + +#endif diff --git a/src/libgsmd/lgsm_internals.h b/src/libgsmd/lgsm_internals.h new file mode 100644 index 0000000..66fd5fe --- /dev/null +++ b/src/libgsmd/lgsm_internals.h @@ -0,0 +1,4 @@ + +struct lgsm_handle { + int fd; +}; diff --git a/src/libgsmd/libgsmd.c b/src/libgsmd/libgsmd.c new file mode 100644 index 0000000..e514617 --- /dev/null +++ b/src/libgsmd/libgsmd.c @@ -0,0 +1,69 @@ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "lgsm_internals.h" + +static int lgsm_open_backend(struct lgsm_handle *lh, const char *device) +{ + int rc; + + if (!strcmp(device, "gsmd")) { + struct sockaddr_un sun; + + /* use unix domain socket to gsm daemon */ + lh->fd = socket(PF_UNIX, GSMD_UNIX_SOCKET_TYPE, 0); + if (lh->fd < 0) + return lh->fd; + + sun.sun_family = AF_UNIX; + memcpy(sun.sun_path, GSMD_UNIX_SOCKET, sizeof(GSMD_UNIX_SOCKET)); + + rc = connect(lh->fd, (struct sockaddr *)&sun, sizeof(sun)); + if (rc < 0) { + close(lh->fd); + lh->fd = -1; + return rc; + } + } else { + /* use direct access to device node ([virtual] tty device) */ + lh->fd = open(device, O_RDWR); + if (lh->fd < 0) + return lh->fd; + } + + return 0; +} + +struct lgsm_handle *lgsm_init(const char *device) +{ + struct lgsm_handle *lh = malloc(sizeof(*lh)); + + memset(lh, 0, sizeof(*lh)); + lh->fd = -1; + + if (lgsm_open_backend(lh, device) < 0) { + free(lh); + return NULL; + } + + /* send some initial commands, such as ATV1 (verbose response) + * and +CRC=1 (which we currently require!) */ + + return lh; +} + +int lgsm_exit(struct lgsm_handle *lh) +{ + free(lh); + + return 0; +} diff --git a/src/libgsmd/libgsmd_input.c b/src/libgsmd/libgsmd_input.c new file mode 100644 index 0000000..134cfea --- /dev/null +++ b/src/libgsmd/libgsmd_input.c @@ -0,0 +1,13 @@ + +#include +#include +#include +#include + +#include + +#include + +#include "lgsm_internals.h" + + diff --git a/src/libgsmd/libgsmd_voicecall.c b/src/libgsmd/libgsmd_voicecall.c new file mode 100644 index 0000000..f59bdff --- /dev/null +++ b/src/libgsmd/libgsmd_voicecall.c @@ -0,0 +1,24 @@ + + +#include + +#include "libgsmd/internals.h" + + +int lgsm_voice_out_init(struct lgsm_handle *lh, + const struct lgsm_addr *number) +{ + /* send ATD command */ + return -EINVAL; +} + +int lgsm_voice_in_accept(struct lgsm_handle *lh) +{ + return -EINVAL; +} + +int lgsm_voice_hangup(struct lgsm_handle *lh) +{ + /* Send ATH0 */ + return -EINVAL; +} -- cgit v1.2.3