diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 310ad459..d9b612e1 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -8,6 +8,9 @@ builtin_modules += example_history builtin_sources += example_history.c endif +builtin_modules += generic_at +builtin_sources += generic_at.c + noinst_LTLIBRARIES = libbuiltin.la libbuiltin_la_SOURCES = $(builtin_sources) @@ -19,7 +22,7 @@ BUILT_SOURCES = builtin.h nodist_libbuiltin_la_SOURCES = $(BUILT_SOURCES) -AM_CFLAGS = -fvisibility=hidden @GLIB_CFLAGS@ @GDBUS_CFLAGS@ +AM_CFLAGS = -fvisibility=hidden @GLIB_CFLAGS@ @GDBUS_CFLAGS@ @GATCHAT_CFLAGS@ INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/src diff --git a/plugins/generic_at.c b/plugins/generic_at.c new file mode 100644 index 00000000..7bcc69d4 --- /dev/null +++ b/plugins/generic_at.c @@ -0,0 +1,566 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static GSList *g_modems = NULL; + +enum transport_type { + TRANSPORT_TYPE_TCP = 0, + TRANSPORT_TYPE_UNIX = 1, + TRANSPORT_TYPE_TTY = 2, +}; + +struct generic_at_data { + enum transport_type type; + union { + struct sockaddr_in tcp; + struct sockaddr_un un; + char *tty; + }; + + char *timeout_command; + int timeout_interval; + char *init_string; + GAtChat *chat; + GIOChannel *io; + unsigned int timeout_watcher; +}; + +static void generic_at_data_free(struct generic_at_data *d) +{ + if (d->type == TRANSPORT_TYPE_TTY) + g_free(d->tty); + + g_free(d->timeout_command); + g_free(d->init_string); + + if (d->chat) + g_at_chat_unref(d->chat); + + g_free(d); +} + +static struct generic_at_data *parse_modem(GKeyFile *keyfile, const char *group) +{ + struct generic_at_data *d; + char *type; + + d = g_new0(struct generic_at_data, 1); + + type = g_key_file_get_string(keyfile, group, "Type", NULL); + + if (type == NULL) + goto error; + + if (!strcmp(type, "tcp")) + d->type = TRANSPORT_TYPE_TCP; + else if (!strcmp(type, "unix")) + d->type = TRANSPORT_TYPE_UNIX; + else if (!strcmp(type, "tty")) + d->type = TRANSPORT_TYPE_TTY; + else + goto error; + + g_free(type); + + switch (d->type) { + case TRANSPORT_TYPE_TCP: + { + in_addr_t inetaddr; + char *address; + int port; + + address = g_key_file_get_string(keyfile, group, "Address", NULL); + + if (address == NULL) + goto error; + + inetaddr = inet_addr(address); + + g_free(address); + + if (inetaddr == INADDR_NONE) + goto error; + + port = g_key_file_get_integer(keyfile, group, "Port", NULL); + + if (port <= 0 || port > 0xffff) + goto error; + + d->tcp.sin_family = AF_INET; + d->tcp.sin_addr.s_addr = inetaddr; + d->tcp.sin_port = htons(port); + + break; + } + + case TRANSPORT_TYPE_UNIX: + { + char *path; + + path = g_key_file_get_string(keyfile, group, "Address", NULL); + + if (strlen(path) >= 108) { + g_free(path); + goto error; + } + + d->un.sun_family = PF_UNIX; + + if (strncmp("x00", path, 3) == 0) + strcpy(d->un.sun_path + 1, path + 3); + else + strcpy(d->un.sun_path, path); + + g_free(path); + + break; + } + + case TRANSPORT_TYPE_TTY: + { + char *node; + + node = g_key_file_get_string(keyfile, group, "Device", NULL); + + d->tty = node; + + break; + } + + default: + break; + } + + d->init_string = g_key_file_get_string(keyfile, group, + "InitString", NULL); + + d->timeout_interval = g_key_file_get_integer(keyfile, group, + "TimeoutInterval", NULL); + + if (d->timeout_interval < 0 || d->timeout_interval > 3600) + goto error; + + d->timeout_command = g_key_file_get_string(keyfile, group, + "TimeoutCommand", NULL); + + return d; + +error: + generic_at_data_free(d); + return NULL; +} + +static void parse_config(const char *file) +{ + GError *err = NULL; + GKeyFile *keyfile; + char **modems; + int i; + + keyfile = g_key_file_new(); + + g_key_file_set_list_separator(keyfile, ','); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + ofono_error("Parsing %s failed: %s", file, err->message); + g_error_free(err); + g_key_file_free(keyfile); + return; + } + + modems = g_key_file_get_groups(keyfile, NULL); + + for (i = 0; modems[i]; i++) { + struct generic_at_data *parsed; + struct ofono_modem *modem; + + DBG("modem: %s", modems[i]); + + parsed = parse_modem(keyfile, modems[i]); + + DBG("parsed: %p", parsed); + + if (parsed == NULL) + continue; + + modem = ofono_modem_create(modems[i], "generic_at"); + + if (modem == NULL) + continue; + + ofono_modem_set_data(modem, parsed); + ofono_modem_register(modem); + } + + g_strfreev(modems); + g_key_file_free(keyfile); +} + +static void connect_destroy(gpointer user) +{ + struct ofono_modem *modem = user; + struct generic_at_data *d = ofono_modem_get_data(modem); + + if (d->timeout_watcher != 0) { + g_source_remove(d->timeout_watcher); + d->timeout_watcher = 0; + } + + d->io = NULL; +} + +static void at_debug(const char *str, void *data) +{ + ofono_debug(str); +} + +static gboolean connect_cb(GIOChannel *io, GIOCondition cond, gpointer user) +{ + struct ofono_modem *modem = user; + struct generic_at_data *d = ofono_modem_get_data(modem); + int err = 0; + gboolean success; + GAtSyntax *syntax; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & G_IO_OUT) { + int sock = g_io_channel_unix_get_fd(io); + socklen_t len = sizeof(err); + + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0) + err = errno == ENOTSOCK ? 0 : errno; + } else if (cond & (G_IO_HUP | G_IO_ERR)) + err = ECONNRESET; + + success = !err; + + DBG("io ref: %d", io->ref_count); + + if (success == FALSE) + goto error; + + syntax = g_at_syntax_new_gsmv1(); + d->chat = g_at_chat_new(io, syntax); + g_at_syntax_unref(syntax); + + DBG("io ref: %d", io->ref_count); + + if (!d->chat) + goto error; + + if (getenv("OFONO_AT_DEBUG") != NULL) + g_at_chat_set_debug(d->chat, at_debug, NULL); + + if (d->timeout_command && d->timeout_interval > 0) + g_at_chat_set_wakeup_command(d->chat, d->timeout_command, 1000, + d->timeout_interval * 1000); + + DBG("%s", d->init_string); + + if (d->init_string) + g_at_chat_send(d->chat, d->init_string, NULL, NULL, NULL, NULL); + + ofono_modem_set_powered(modem, TRUE); + + return FALSE; + +error: + ofono_modem_set_powered(modem, FALSE); + return FALSE; +} + +static gboolean connect_timeout(gpointer user) +{ + struct ofono_modem *modem = user; + struct generic_at_data *d = ofono_modem_get_data(modem); + + d->timeout_watcher = 0; + g_io_channel_close(d->io); + ofono_modem_set_powered(modem, FALSE); + + return FALSE; +} + +static GIOChannel *tty_connect(const char *tty) +{ + GIOChannel *io; + int sk; + struct termios newtio; + + sk = open(tty, O_RDWR | O_NOCTTY); + + if (sk < 0) { + ofono_error("Can't open TTY %s: %s(%d)", + tty, strerror(errno), errno); + return NULL; + } + + newtio.c_cflag = B115200 | CRTSCTS | CLOCAL | CREAD; + newtio.c_iflag = IGNPAR; + newtio.c_oflag = 0; + newtio.c_lflag = 0; + + newtio.c_cc[VTIME] = 1; + newtio.c_cc[VMIN] = 5; + + tcflush(sk, TCIFLUSH); + if (tcsetattr(sk, TCSANOW, &newtio) < 0) { + ofono_error("Can't change serial settings: %s(%d)", + strerror(errno), errno); + close(sk); + return NULL; + } + + io = g_io_channel_unix_new(sk); + g_io_channel_set_close_on_unref(io, TRUE); + + if (g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, + NULL) != G_IO_STATUS_NORMAL) { + g_io_channel_unref(io); + return NULL; + } + + return io; +} + +static GIOChannel *socket_common(int sk, struct sockaddr *addr, + socklen_t addrlen) +{ + GIOChannel *io = g_io_channel_unix_new(sk); + + if (io == NULL) { + close(sk); + return NULL; + } + + g_io_channel_set_close_on_unref(io, TRUE); + + if (g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, + NULL) != G_IO_STATUS_NORMAL) { + g_io_channel_unref(io); + return NULL; + } + + if (connect(sk, addr, addrlen) < 0) { + if (errno != EAGAIN && errno != EINPROGRESS) { + g_io_channel_unref(io); + return NULL; + } + } + + return io; +} + +static int generic_at_probe(struct ofono_modem *modem) +{ + return 0; +} + +static int generic_at_remove(struct ofono_modem *modem) +{ + return 0; +} + +static int generic_at_enable(struct ofono_modem *modem) +{ + struct generic_at_data *d = ofono_modem_get_data(modem); + int sk; + GIOChannel *io; + GIOCondition cond; + + DBG(""); + + switch (d->type) { + case TRANSPORT_TYPE_TCP: + sk = socket(PF_INET, SOCK_STREAM, 0); + + if (sk < 0) + return -EAFNOSUPPORT; + + io = socket_common(sk, (struct sockaddr *)&d->tcp, + sizeof(d->tcp)); + break; + + case TRANSPORT_TYPE_UNIX: + sk = socket(AF_UNIX, SOCK_STREAM, 0); + + if (sk < 0) + return -EAFNOSUPPORT; + + io = socket_common(sk, (struct sockaddr *)&d->tcp, + sizeof(d->tcp)); + + break; + + case TRANSPORT_TYPE_TTY: + io = tty_connect(d->tty); + + break; + + default: + io = NULL; + } + + if (io == NULL) + return -EINVAL; + + DBG("io ref: %d", io->ref_count); + + d->timeout_watcher = g_timeout_add_seconds(10, connect_timeout, modem); + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, + modem, connect_destroy); + + DBG("io ref: %d", io->ref_count); + + g_io_channel_unref(io); + + DBG("io ref: %d", io->ref_count); + d->io = io; + + return -EINPROGRESS; +} + +static int generic_at_disable(struct ofono_modem *modem) +{ + struct generic_at_data *d = ofono_modem_get_data(modem); + + if (d->io) { + g_io_channel_close(d->io); + d->io = NULL; + } + + if (d->chat) { + g_at_chat_unref(d->chat); + d->chat = NULL; + } + + return 0; +} + +static int generic_at_populate(struct ofono_modem *modem) +{ + struct generic_at_data *d = ofono_modem_get_data(modem); + GAtChat *chat = d->chat; + struct ofono_message_waiting *mw; + + ofono_devinfo_create(modem, "generic_at", chat); + ofono_ussd_create(modem, "generic_at", chat); + ofono_sim_create(modem, "generic_at", chat); + ofono_call_forwarding_create(modem, "generic_at", chat); + ofono_call_settings_create(modem, "generic_at", chat); + ofono_netreg_create(modem, "generic_at", chat); + ofono_voicecall_create(modem, "generic_at", chat); + ofono_call_meter_create(modem, "generic_at", chat); + ofono_call_barring_create(modem, "generic_at", chat); + ofono_ssn_create(modem, "generic_at", chat); + ofono_sms_create(modem, "generic_at", chat); + ofono_phonebook_create(modem, "generic_at", chat); + + mw = ofono_message_waiting_create(modem); + if (mw) + ofono_message_waiting_register(mw); + + return 0; +} + +static struct ofono_modem_driver driver = { + .name = "generic_at", + .probe = generic_at_probe, + .remove = generic_at_remove, + .enable = generic_at_enable, + .disable = generic_at_disable, + .populate = generic_at_populate, +}; + +static int generic_at_init(void) +{ + int err = -EIO; + + err = ofono_modem_driver_register(&driver); + + if (err < 0) + return err; + + parse_config(CONFIGDIR "/generic_at.conf"); + + return 0; +} + +static void generic_at_exit(void) +{ + GSList *l; + struct ofono_modem *modem; + + for (l = g_modems; l; l = l->next) { + modem = l->data; + + ofono_modem_remove(modem); + } + + g_slist_free(g_modems); + + ofono_modem_driver_unregister(&driver); +} + +OFONO_PLUGIN_DEFINE(generic_at, "Generic AT Modem driver", VERSION, + OFONO_PLUGIN_PRIORITY_DEFAULT, + generic_at_init, generic_at_exit) diff --git a/plugins/generic_at.conf b/plugins/generic_at.conf new file mode 100644 index 00000000..acbb7865 --- /dev/null +++ b/plugins/generic_at.conf @@ -0,0 +1,28 @@ +# This is a sample configuration file for the generic_at plugin +# It should be installed in your ofono sysconfdir, e.g. +# /etc/ofono/generic_at.conf +# Each group is parsed as a device +# Each group can contain the following keys: +# Type = tcp | unix | tty +# InitString = , e.g. ATZ +# +# If Type is tcp, the following keys are available: +# Address = +# +# If Type is unix: +# Address = UNIX path, prefixed by x00 for anonymous paths +# +# If Type is tty: +# Device = + +#[phonesim] +#Type=tcp +#Address=127.0.0.1 +#Port=12345 + +#Sample TTY for OpenMoko calypso +#Type=tty +#Device=/dev/ttySAC0 +#InitString=AT+CFUN=1 +#TimeoutCommand=\r +#TimeoutInterval=5