diff --git a/linux/gpio.h b/linux/gpio.h new file mode 100644 index 000000000..778ebc356 --- /dev/null +++ b/linux/gpio.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * - userspace ABI for the GPIO character devices + * + * Copyright (C) 2016 Linus Walleij + * + * 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. + */ +#ifndef _GPIO_H_ +#define _GPIO_H_ + +#include +#include + +/** + * struct gpiochip_info - Information about a certain GPIO chip + * @name: the Linux kernel name of this GPIO chip + * @label: a functional name for this GPIO chip, such as a product + * number, may be NULL + * @lines: number of GPIO lines on this chip + */ +struct gpiochip_info { + char name[32]; + char label[32]; + __u32 lines; +}; + +/* Informational flags */ +#define GPIOLINE_FLAG_KERNEL (1UL << 0) /* Line used by the kernel */ +#define GPIOLINE_FLAG_IS_OUT (1UL << 1) +#define GPIOLINE_FLAG_ACTIVE_LOW (1UL << 2) +#define GPIOLINE_FLAG_OPEN_DRAIN (1UL << 3) +#define GPIOLINE_FLAG_OPEN_SOURCE (1UL << 4) + +/** + * struct gpioline_info - Information about a certain GPIO line + * @line_offset: the local offset on this GPIO device, fill this in when + * requesting the line information from the kernel + * @flags: various flags for this line + * @name: the name of this GPIO line, such as the output pin of the line on the + * chip, a rail or a pin header name on a board, as specified by the gpio + * chip, may be NULL + * @consumer: a functional name for the consumer of this GPIO line as set by + * whatever is using it, will be NULL if there is no current user but may + * also be NULL if the consumer doesn't set this up + */ +struct gpioline_info { + __u32 line_offset; + __u32 flags; + char name[32]; + char consumer[32]; +}; + +/* Maximum number of requested handles */ +#define GPIOHANDLES_MAX 64 + +/* Linerequest flags */ +#define GPIOHANDLE_REQUEST_INPUT (1UL << 0) +#define GPIOHANDLE_REQUEST_OUTPUT (1UL << 1) +#define GPIOHANDLE_REQUEST_ACTIVE_LOW (1UL << 2) +#define GPIOHANDLE_REQUEST_OPEN_DRAIN (1UL << 3) +#define GPIOHANDLE_REQUEST_OPEN_SOURCE (1UL << 4) + +/** + * struct gpiohandle_request - Information about a GPIO handle request + * @lineoffsets: an array of desired lines, specified by offset index for the + * associated GPIO device + * @flags: desired flags for the desired GPIO lines, such as + * GPIOHANDLE_REQUEST_OUTPUT, GPIOHANDLE_REQUEST_ACTIVE_LOW etc, OR:ed + * together. Note that even if multiple lines are requested, the same flags + * must be applicable to all of them, if you want lines with individual + * flags set, request them one by one. It is possible to select + * a batch of input or output lines, but they must all have the same + * characteristics, i.e. all inputs or all outputs, all active low etc + * @default_values: if the GPIOHANDLE_REQUEST_OUTPUT is set for a requested + * line, this specifies the default output value, should be 0 (low) or + * 1 (high), anything else than 0 or 1 will be interpreted as 1 (high) + * @consumer_label: a desired consumer label for the selected GPIO line(s) + * such as "my-bitbanged-relay" + * @lines: number of lines requested in this request, i.e. the number of + * valid fields in the above arrays, set to 1 to request a single line + * @fd: if successful this field will contain a valid anonymous file handle + * after a GPIO_GET_LINEHANDLE_IOCTL operation, zero or negative value + * means error + */ +struct gpiohandle_request { + __u32 lineoffsets[GPIOHANDLES_MAX]; + __u32 flags; + __u8 default_values[GPIOHANDLES_MAX]; + char consumer_label[32]; + __u32 lines; + int fd; +}; + +/** + * struct gpiohandle_data - Information of values on a GPIO handle + * @values: when getting the state of lines this contains the current + * state of a line, when setting the state of lines these should contain + * the desired target state + */ +struct gpiohandle_data { + __u8 values[GPIOHANDLES_MAX]; +}; + +#define GPIOHANDLE_GET_LINE_VALUES_IOCTL _IOWR(0xB4, 0x08, struct gpiohandle_data) +#define GPIOHANDLE_SET_LINE_VALUES_IOCTL _IOWR(0xB4, 0x09, struct gpiohandle_data) + +/* Eventrequest flags */ +#define GPIOEVENT_REQUEST_RISING_EDGE (1UL << 0) +#define GPIOEVENT_REQUEST_FALLING_EDGE (1UL << 1) +#define GPIOEVENT_REQUEST_BOTH_EDGES ((1UL << 0) | (1UL << 1)) + +/** + * struct gpioevent_request - Information about a GPIO event request + * @lineoffset: the desired line to subscribe to events from, specified by + * offset index for the associated GPIO device + * @handleflags: desired handle flags for the desired GPIO line, such as + * GPIOHANDLE_REQUEST_ACTIVE_LOW or GPIOHANDLE_REQUEST_OPEN_DRAIN + * @eventflags: desired flags for the desired GPIO event line, such as + * GPIOEVENT_REQUEST_RISING_EDGE or GPIOEVENT_REQUEST_FALLING_EDGE + * @consumer_label: a desired consumer label for the selected GPIO line(s) + * such as "my-listener" + * @fd: if successful this field will contain a valid anonymous file handle + * after a GPIO_GET_LINEEVENT_IOCTL operation, zero or negative value + * means error + */ +struct gpioevent_request { + __u32 lineoffset; + __u32 handleflags; + __u32 eventflags; + char consumer_label[32]; + int fd; +}; + +/** + * GPIO event types + */ +#define GPIOEVENT_EVENT_RISING_EDGE 0x01 +#define GPIOEVENT_EVENT_FALLING_EDGE 0x02 + +/** + * struct gpioevent_data - The actual event being pushed to userspace + * @timestamp: best estimate of time of event occurrence, in nanoseconds + * @id: event identifier + */ +struct gpioevent_data { + __u64 timestamp; + __u32 id; +}; + +#define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info) +#define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info) +#define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request) +#define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request) + +#endif /* _GPIO_H_ */ diff --git a/linux/gsmmux.h b/linux/gsmmux.h new file mode 100644 index 000000000..101d3c469 --- /dev/null +++ b/linux/gsmmux.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_GSMMUX_H +#define _LINUX_GSMMUX_H + +#include +#include +#include + +struct gsm_config +{ + unsigned int adaption; + unsigned int encapsulation; + unsigned int initiator; + unsigned int t1; + unsigned int t2; + unsigned int t3; + unsigned int n2; + unsigned int mru; + unsigned int mtu; + unsigned int k; + unsigned int i; + unsigned int unused[8]; /* Padding for expansion without + breaking stuff */ +}; + +#define GSMIOC_GETCONF _IOR('G', 0, struct gsm_config) +#define GSMIOC_SETCONF _IOW('G', 1, struct gsm_config) + +struct gsm_netconfig { + unsigned int adaption; /* Adaption to use in network mode */ + unsigned short protocol;/* Protocol to use - only ETH_P_IP supported */ + unsigned short unused2; + char if_name[IFNAMSIZ]; /* interface name format string */ + __u8 unused[28]; /* For future use */ +}; + +#define GSMIOC_ENABLE_NET _IOW('G', 2, struct gsm_netconfig) +#define GSMIOC_DISABLE_NET _IO('G', 3) + + +#endif diff --git a/ofono/.gitignore b/ofono/.gitignore index 1bef6a9b4..6e75fe9f7 100644 --- a/ofono/.gitignore +++ b/ofono/.gitignore @@ -36,7 +36,6 @@ test-driver test-suite.log unit/test-common unit/test-util -unit/test-idmap unit/test-sms unit/test-sms-root unit/test-simutil diff --git a/ofono/AUTHORS b/ofono/AUTHORS index 758d571e7..796b2680f 100644 --- a/ofono/AUTHORS +++ b/ofono/AUTHORS @@ -126,6 +126,7 @@ Lukasz Nowak Jonas Bonn Matthijs Kooijman Clayton Craft +Alexander Couzens Joey Hewitt Richard Röjfors Philippe De Swert @@ -139,5 +140,8 @@ Martin Hundebøll Julien Tournier Nandini Rebello Giacinto Cifelli +Pau Espin Pedrol Khaled Romdhani Pavel Machek +Tom Nguyen +Stefan Herbrechtsmeier diff --git a/ofono/ChangeLog b/ofono/ChangeLog index e2937659c..0fc32a86d 100644 --- a/ofono/ChangeLog +++ b/ofono/ChangeLog @@ -1,3 +1,20 @@ +ver 1.31: + Fix issue with handling CID 0 context identifier. + Fix issue with handling detach state and running LTE. + Fix issue with handling SIM states and Quectel modems. + +ver 1.30: + Fix issue with handling of IPv6 address and xmm7xxx modems. + Fix issue with default context creation with an empty APN. + Fix issue with activation of context with CID zero. + Add support for Quectel MC60 modems. + +ver 1.29: + Fix issue with QMI and SIM initialized notification. + Add support for multiple PDP contexts and xmm7xxx modems. + Add support for handling Dual SIM Single Active feature + Add support for SIM PIN caching feature. + ver 1.28: Fix issue with SIM initialization and Gemalto modems. diff --git a/ofono/Makefile.am b/ofono/Makefile.am index d067817db..088932e62 100644 --- a/ofono/Makefile.am +++ b/ofono/Makefile.am @@ -267,6 +267,7 @@ builtin_sources += drivers/atmodem/atmodem.h \ drivers/atmodem/cbs.c \ drivers/atmodem/call-forwarding.c \ drivers/atmodem/call-meter.c \ + drivers/atmodem/network-registration.h \ drivers/atmodem/network-registration.c \ drivers/atmodem/sim.c \ drivers/atmodem/stk.c \ @@ -404,6 +405,7 @@ builtin_sources += drivers/atmodem/atutil.h \ drivers/ubloxmodem/ubloxmodem.h \ drivers/ubloxmodem/ubloxmodem.c \ drivers/ubloxmodem/gprs-context.c \ + drivers/ubloxmodem/network-registration.c \ drivers/ubloxmodem/netmon.c \ drivers/ubloxmodem/lte.c @@ -695,7 +697,8 @@ endif sbin_PROGRAMS = src/ofonod -src_ofonod_SOURCES = $(builtin_sources) $(gatchat_sources) src/ofono.ver \ +src_ofonod_SOURCES = $(builtin_sources) $(gatchat_sources) \ + linux/gsmmux.h linux/gpio.h src/ofono.ver \ src/main.c src/ofono.h src/log.c src/plugin.c \ src/modem.c src/common.h src/common.c \ src/manager.c src/dbus.c src/util.h src/util.c \ diff --git a/ofono/configure.ac b/ofono/configure.ac index 302815a53..3b0d6c550 100644 --- a/ofono/configure.ac +++ b/ofono/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ(2.60) -AC_INIT(ofono, 1.28) +AC_INIT(ofono, 1.31) AM_INIT_AUTOMAKE([foreign subdir-objects color-tests]) AC_CONFIG_HEADERS(config.h) @@ -55,6 +55,8 @@ AC_ARG_ENABLE(pie, AC_HELP_STRING([--enable-pie], fi ]) +AC_CHECK_FUNCS(explicit_bzero) + AC_CHECK_FUNC(signalfd, dummy=yes, AC_MSG_ERROR(signalfd support is required)) diff --git a/ofono/doc/networkmonitor-api.txt b/ofono/doc/networkmonitor-api.txt index f8cc1c2e9..1cc73d2ca 100644 --- a/ofono/doc/networkmonitor-api.txt +++ b/ofono/doc/networkmonitor-api.txt @@ -22,6 +22,23 @@ Methods a{sv} GetServingCellInformation() are available, their valid value ranges and applicability to different cell types. + a{a{sv}} GetNeighbouringCellsInformation() + + Requests the neighbouring cells information and basic + measurements from oFono. The returned value is a + dictionary with the possible key / values documented + below. The type of cell is given by the 'Technology' + property. + + Based on the type of cell, the dictionary will contain + additional key/value pairs. If a given key/value pair + is not present, then it is not known or unsupported + by the underlying driver. + + Refer to the sections below for which property types + are available, their valid value ranges and + applicability to different cell types. + void RegisterAgent(object path) Registers an agent which will be called whenever the diff --git a/ofono/doc/quectel-hardware-api.txt b/ofono/doc/quectel-hardware-api.txt new file mode 100644 index 000000000..c24332820 --- /dev/null +++ b/ofono/doc/quectel-hardware-api.txt @@ -0,0 +1,51 @@ +Hardware hierarchy +================== + +Service org.ofono +Interface org.ofono.quectel.Hardware +Object path /{device0,device1,...} + +Methods array{string,variant} GetProperties + + Returns hardware properties for the modem object. See + the properties section for available properties. + +Signals PowerDown(string reason) + + This signal is emitted on gracefull shutdowns initiated + by the modem. + + Possible reasons: + "voltagelow" The supply voltage is too low + "normal" The PWRKEY pin was asserted + "voltagehigh" The supply voltage is too high + + PowerWarning(string reason) + + This signal is emitted when the modem detects its supply + voltage is close to its supported limits. + + Possible reasons: + "voltagelow" The supply voltage is low + "voltagehigh" The supply voltage is high + +Properties int32 Voltage [readonly] + + Integer with the modem supply voltage in mV. + + int32 ChargeStatus [readonly,optional] + + Integer with one of either: + + 0: Modem is not charging + 1: Modem is charging + 2: Charging is finished + + This property is available on UC15 + + int32 ChargeLevel [readonly,optional] + + Integer representing the battery charge level in + percent (from 0 to 100). + + This property is available on UC15 diff --git a/ofono/doc/radio-settings-api.txt b/ofono/doc/radio-settings-api.txt index 03868a9b1..9663b07f9 100644 --- a/ofono/doc/radio-settings-api.txt +++ b/ofono/doc/radio-settings-api.txt @@ -44,6 +44,12 @@ Properties string TechnologyPreference [readwrite] "gsm" Only GSM used for radio access. "umts" Only UMTS used for radio access. "lte" Only LTE used for radio access. + "umts,gsm" Dual mode operation with UMTS + and GSM radio access whith preference + for UMTS. + "lte,umts" Dual mode operation with LTE + and UMTS radio access with preference + for LTE. array{string} AvailableTechnologies [readonly, optional] diff --git a/ofono/doc/sim-api.txt b/ofono/doc/sim-api.txt index bce47c121..b1428b10f 100644 --- a/ofono/doc/sim-api.txt +++ b/ofono/doc/sim-api.txt @@ -205,3 +205,15 @@ Properties boolean Present [readonly] Contains the SIM's ImsPrivateIdentity, read from the ISIM. + + uint32 CardSlotCount [readonly, experimental] + + Contains the count of number of SIM card slots available. + + uint32 ActiveCardSlot [readwrite, experimental] + + Contains the index of the currently active SIM card slot + for dual SIM single active mode. + + This property will range from 1 (default) to + CardSlotCount (max) value. diff --git a/ofono/drivers/atmodem/atutil.c b/ofono/drivers/atmodem/atutil.c index fa9ed72f7..073f86b31 100644 --- a/ofono/drivers/atmodem/atutil.c +++ b/ofono/drivers/atmodem/atutil.c @@ -24,15 +24,17 @@ #include #endif -#include -#include #include #include #include +#include +#include + #define OFONO_API_SUBJECT_TO_CHANGE #include #include +#include #include "atutil.h" #include "vendor.h" @@ -656,6 +658,45 @@ int at_util_get_ipv4_address_and_netmask(const char *addrnetmask, return ret; } +/* + * CGCONTRDP returns addr + netmask in the same string in the form + * of "a1.a2.a3.a4.a5.a6.a7.a8.a9.a10.a11.a12.a13.a14.a15.a16.m1.m2. + * m3.m4.m5.m6.m7.m8.m9.m10.m11.m12.m13.m14.m15.m16" for IPv6. + * address/netmask must be able to hold 64 characters. + */ +int at_util_get_ipv6_address_and_netmask(const char *addrnetmask, + char *address, char *netmask) +{ + const char *s = addrnetmask; + const char *net = NULL; + + int ret = -EINVAL; + int i; + + /* Count 31 dots for ipv6, less or more means error. */ + for (i = 0; i < 33; i++, s++) { + s = strchr(s, '.'); + + if (!s) + break; + + if (i == 15) { + /* set netmask ptr and break the string */ + net = s + 1; + } + } + + if (i == 31) { + memcpy(address, addrnetmask, net - addrnetmask); + address[net - addrnetmask - 1] = '\0'; + strcpy(netmask, net); + + ret = 0; + } + + return ret; +} + int at_util_gprs_auth_method_to_auth_prot( enum ofono_gprs_auth_method auth_method) { @@ -698,3 +739,54 @@ char *at_util_get_cgdcont_command(guint cid, enum ofono_gprs_proto proto, return g_strdup_printf("AT+CGDCONT=%u,\"%s\",\"%s\"", cid, pdp_type, apn); } + +GAtChat *at_util_open_device(struct ofono_modem *modem, const char *key, + GAtDebugFunc debug_func, char *debug_prefix, + char *tty_option, ...) +{ + const char *device; + va_list args; + GIOChannel *channel; + GAtSyntax *syntax; + GAtChat *chat; + GHashTable *options = NULL; + + device = ofono_modem_get_string(modem, key); + if (device == NULL) + return NULL; + + if (tty_option) { + options = g_hash_table_new(g_str_hash, g_str_equal); + if (options == NULL) + return NULL; + + va_start(args, tty_option); + while (tty_option) { + gpointer value = (gpointer) va_arg(args, const char *); + + g_hash_table_insert(options, tty_option, value); + tty_option = (gpointer) va_arg(args, const char *); + } + } + + channel = g_at_tty_open(device, options); + + if (options) + g_hash_table_destroy(options); + + if (channel == NULL) + return NULL; + + syntax = g_at_syntax_new_gsm_permissive(); + chat = g_at_chat_new(channel, syntax); + g_at_syntax_unref(syntax); + g_io_channel_unref(channel); + + if (chat == NULL) + return NULL; + + if (getenv("OFONO_AT_DEBUG")) + g_at_chat_set_debug(chat, debug_func, debug_prefix); + + return chat; +} diff --git a/ofono/drivers/atmodem/atutil.h b/ofono/drivers/atmodem/atutil.h index 69e8b499d..fe2acb390 100644 --- a/ofono/drivers/atmodem/atutil.h +++ b/ofono/drivers/atmodem/atutil.h @@ -20,6 +20,10 @@ * */ +#include + +struct ofono_modem; + enum at_util_sms_store { AT_UTIL_SMS_STORE_SM = 0, AT_UTIL_SMS_STORE_ME = 1, @@ -86,6 +90,9 @@ void at_util_sim_state_query_free(struct at_util_sim_state_query *req); int at_util_get_ipv4_address_and_netmask(const char *addrnetmask, char *address, char *netmask); +int at_util_get_ipv6_address_and_netmask(const char *addrnetmask, + char *address, char *netmask); + int at_util_gprs_auth_method_to_auth_prot( enum ofono_gprs_auth_method auth_method); @@ -166,3 +173,7 @@ static inline int at_util_convert_signal_strength(int strength) e.error = 0; \ f(&e, ##args); \ } while (0) + +GAtChat *at_util_open_device(struct ofono_modem *modem, const char *key, + GAtDebugFunc debug_func, char *debug_prefix, + char *tty_option, ...); diff --git a/ofono/drivers/atmodem/gprs-context.c b/ofono/drivers/atmodem/gprs-context.c index b86c63893..403a300dd 100644 --- a/ofono/drivers/atmodem/gprs-context.c +++ b/ofono/drivers/atmodem/gprs-context.c @@ -285,35 +285,36 @@ static void at_gprs_activate_primary(struct ofono_gprs_context *gc, len = snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\"", ctx->cid); - if (ctx->apn) { - switch (gcd->vendor) { - case OFONO_VENDOR_UBLOX: - /* - * U-blox modems require a magic prefix to the APN to - * specify the authentication method to use in the - * network. See UBX-13002752 - R21. - * - * As the response of the read command omits this magic - * prefix, this is the least invasive place to set it. - */ - switch (ctx->auth_method) { - case OFONO_GPRS_AUTH_METHOD_ANY: - case OFONO_GPRS_AUTH_METHOD_NONE: - case OFONO_GPRS_AUTH_METHOD_CHAP: - snprintf(buf + len, sizeof(buf) - len - 3, - ",\"CHAP:%s\"", ctx->apn); - break; - case OFONO_GPRS_AUTH_METHOD_PAP: - snprintf(buf + len, sizeof(buf) - len - 3, - ",\"PAP:%s\"", ctx->apn); - break; - } + switch (gcd->vendor) { + case OFONO_VENDOR_UBLOX: + /* + * U-blox modems require a magic prefix to the APN to + * specify the authentication method to use in the + * network. See UBX-13002752 - R21. + * + * As the response of the read command omits this magic + * prefix, this is the least invasive place to set it. + */ + switch (ctx->auth_method) { + case OFONO_GPRS_AUTH_METHOD_ANY: + case OFONO_GPRS_AUTH_METHOD_CHAP: + snprintf(buf + len, sizeof(buf) - len - 3, + ",\"CHAP:%s\"", ctx->apn); break; - default: - snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", - ctx->apn); + case OFONO_GPRS_AUTH_METHOD_PAP: + snprintf(buf + len, sizeof(buf) - len - 3, + ",\"PAP:%s\"", ctx->apn); + break; + case OFONO_GPRS_AUTH_METHOD_NONE: + snprintf(buf + len, sizeof(buf) - len - 3, + ",\"%s\"", ctx->apn); break; } + break; + default: + snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", + ctx->apn); + break; } if (g_at_chat_send(gcd->chat, buf, none_prefix, @@ -405,9 +406,11 @@ static void at_cgdata_test_cb(gboolean ok, GAtResult *result, goto error; } - if (!g_at_result_iter_open_list(&iter)) { - DBG("no list found"); - goto error; + if (gcd->vendor != OFONO_VENDOR_QUECTEL_SERIAL) { + if (!g_at_result_iter_open_list(&iter)) { + DBG("no list found"); + goto error; + } } while (!found && g_at_result_iter_next_string(&iter, &data_type)) { diff --git a/ofono/drivers/atmodem/gprs.c b/ofono/drivers/atmodem/gprs.c index de7c78640..673a8b3cf 100644 --- a/ofono/drivers/atmodem/gprs.c +++ b/ofono/drivers/atmodem/gprs.c @@ -29,6 +29,7 @@ #include #include +#include "idmap.h" #include #include @@ -41,18 +42,63 @@ #include "atmodem.h" #include "vendor.h" +#define MAX_CONTEXTS 255 + static const char *cgreg_prefix[] = { "+CGREG:", NULL }; +static const char *cgerep_prefix[] = { "+CGEREP:", NULL }; static const char *cgdcont_prefix[] = { "+CGDCONT:", NULL }; +static const char *cgact_prefix[] = { "+CGACT:", NULL }; static const char *none_prefix[] = { NULL }; struct gprs_data { GAtChat *chat; unsigned int vendor; - unsigned int last_auto_context_id; + int last_auto_context_id; gboolean telit_try_reattach; int attached; }; +struct list_contexts_data +{ + struct ofono_gprs *gprs; + void *cb; + void *data; + struct idmap *active_cids; + int ref_count; +}; + +static struct list_contexts_data * list_contexts_data_new( + struct ofono_gprs *gprs, void *cb, void *data) +{ + struct list_contexts_data *ret; + + ret = g_new0(struct list_contexts_data, 1); + ret->ref_count = 1; + ret->gprs = gprs; + ret->cb = cb; + ret->data = data; + + return ret; +} + +static struct list_contexts_data * list_contexts_data_ref( + struct list_contexts_data *ld) +{ + ld->ref_count++; + return ld; +} + +static void list_contexts_data_unref(gpointer user_data) +{ + struct list_contexts_data *ld = user_data; + + if (--ld->ref_count) + return; + + idmap_free(ld->active_cids); + g_free(ld); +} + static void at_cgatt_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; @@ -145,14 +191,43 @@ static void at_gprs_registration_status(struct ofono_gprs *gprs, CALLBACK_WITH_FAILURE(cb, -1, data); } +static void at_cgdcont_parse(struct ofono_gprs *gprs, GAtResult *result, + struct idmap *cids) +{ + GAtResultIter iter; + + g_at_result_iter_init(&iter, result); + + while (g_at_result_iter_next(&iter, "+CGDCONT:")) { + int read_cid; + const char *apn = NULL; + + if (!g_at_result_iter_next_number(&iter, &read_cid)) + break; + + if (!idmap_find(cids, read_cid)) + continue; + + /* ignore protocol */ + g_at_result_iter_skip_next(&iter); + + g_at_result_iter_next_string(&iter, &apn); + + if (apn) + ofono_gprs_cid_activated(gprs, read_cid, apn); + else + ofono_warn("cid %d: Activated but no apn present", + read_cid); + } +} + static void at_cgdcont_read_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_gprs *gprs = user_data; struct gprs_data *gd = ofono_gprs_get_data(gprs); int activated_cid = gd->last_auto_context_id; - const char *apn = NULL; - GAtResultIter iter; + struct idmap *cids; DBG("ok %d", ok); @@ -161,30 +236,108 @@ static void at_cgdcont_read_cb(gboolean ok, GAtResult *result, return; } + if (activated_cid == -1) { + DBG("Context got deactivated while calling CGDCONT"); + return; + } + + cids = idmap_new(activated_cid); + + idmap_take(cids, activated_cid); + + at_cgdcont_parse(gprs, result, cids); + + idmap_free(cids); +} + +static void at_cgdcont_act_read_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct list_contexts_data *ld = user_data; + ofono_gprs_cb_t cb = ld->cb; + struct ofono_error error; + + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) + ofono_warn("Can't read CGDCONT context."); + else + at_cgdcont_parse(ld->gprs, result, ld->active_cids); + + cb(&error, ld->data); +} + +static void at_cgact_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct list_contexts_data *ld = user_data; + struct gprs_data *gd = ofono_gprs_get_data(ld->gprs); + ofono_gprs_cb_t cb = ld->cb; + struct ofono_error error; + GAtResultIter iter; + + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + ofono_warn("Can't read CGACT contexts."); + + cb(&error, ld->data); + + return; + } + g_at_result_iter_init(&iter, result); - while (g_at_result_iter_next(&iter, "+CGDCONT:")) { - int read_cid; + while (g_at_result_iter_next(&iter, "+CGACT:")) { + int read_cid = -1; + int read_status = -1; if (!g_at_result_iter_next_number(&iter, &read_cid)) break; - if (read_cid != activated_cid) + if (!g_at_result_iter_next_number(&iter, &read_status)) + break; + + if (read_status != 1) continue; - /* ignore protocol */ - g_at_result_iter_skip_next(&iter); + /* Flag this as auto context as it was obviously active */ + if (gd->last_auto_context_id == -1) + gd->last_auto_context_id = read_cid; - g_at_result_iter_next_string(&iter, &apn); + if (!ld->active_cids) + ld->active_cids = idmap_new(MAX_CONTEXTS); - break; + idmap_take(ld->active_cids, read_cid); } - if (apn) - ofono_gprs_cid_activated(gprs, activated_cid, apn); - else - ofono_warn("cid %u: Received activated but no apn present", - activated_cid); + if (ld->active_cids != NULL) { + if (g_at_chat_send(gd->chat, "AT+CGDCONT?", cgdcont_prefix, + at_cgdcont_act_read_cb, ld, + list_contexts_data_unref)) { + list_contexts_data_ref(ld); + return; + } + + CALLBACK_WITH_FAILURE(cb, ld->data); + } else { + /* No active contexts found */ + cb(&error, ld->data); + } +} + +static void at_gprs_list_active_contexts(struct ofono_gprs *gprs, + ofono_gprs_cb_t cb, void *data) +{ + struct gprs_data *gd = ofono_gprs_get_data(gprs); + struct list_contexts_data *ld = list_contexts_data_new(gprs, cb, data); + + if (g_at_chat_send(gd->chat, "AT+CGACT?", cgact_prefix, + at_cgact_cb, ld, list_contexts_data_unref)) + return; + + list_contexts_data_unref(ld); + + CALLBACK_WITH_FAILURE(cb, data); } static void cgreg_notify(GAtResult *result, gpointer user_data) @@ -251,6 +404,12 @@ static void cgev_notify(GAtResult *result, gpointer user_data) g_at_chat_send(gd->chat, "AT+CGDCONT?", cgdcont_prefix, at_cgdcont_read_cb, gprs, NULL); + } else if (g_str_has_prefix(event, "ME PDN DEACT")) { + int context_id; + sscanf(event, "%*s %*s %*s %u", &context_id); + /* Indicate that this cid is not activated anymore */ + if (gd->last_auto_context_id == context_id) + gd->last_auto_context_id = -1; } } @@ -455,7 +614,6 @@ static void gprs_initialized(gboolean ok, GAtResult *result, gpointer user_data) FALSE, gprs, NULL); break; case OFONO_VENDOR_UBLOX: - case OFONO_VENDOR_UBLOX_TOBY_L2: g_at_chat_register(gd->chat, "+UREG:", ublox_ureg_notify, FALSE, gprs, NULL); g_at_chat_send(gd->chat, "AT+UREG=1", none_prefix, @@ -467,6 +625,9 @@ static void gprs_initialized(gboolean ok, GAtResult *result, gpointer user_data) g_at_chat_send(gd->chat, "AT#PSNT=1", none_prefix, NULL, NULL, NULL); break; + case OFONO_VENDOR_QUECTEL_EC2X: + case OFONO_VENDOR_QUECTEL_SERIAL: + break; default: g_at_chat_register(gd->chat, "+CPSB:", cpsb_notify, FALSE, gprs, NULL); @@ -488,6 +649,65 @@ static void gprs_initialized(gboolean ok, GAtResult *result, gpointer user_data) ofono_gprs_register(gprs); } +static void at_cgerep_test_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_gprs *gprs = user_data; + struct gprs_data *gd = ofono_gprs_get_data(gprs); + GAtResultIter iter; + int min, max, arg1 = 0, arg2 = 0; + gboolean two_arguments = TRUE; + char buf[20]; + + if (!ok) { + ofono_error("Error querying AT+CGEREP=? Failing..."); + ofono_gprs_remove(gprs); + return; + } + + g_at_result_iter_init(&iter, result); + + g_at_result_iter_next(&iter, "+CGEREP:"); + + if (!g_at_result_iter_open_list(&iter)) { + ofono_error("Malformed reply from AT+CGEREP=? Failing..."); + ofono_gprs_remove(gprs); + return; + } + + while (g_at_result_iter_next_range(&iter, &min, &max)) { + if ((min <= 1) && (max >= 1)) + arg1 = 1; + + if ((min <= 2) && (max >= 2)) + arg1 = 2; + } + + if (!g_at_result_iter_close_list(&iter)) + goto out; + + if (!g_at_result_iter_open_list(&iter)) { + two_arguments = FALSE; + goto out; + } + + while (g_at_result_iter_next_range(&iter, &min, &max)) { + if ((min <= 1) && (max >= 1)) + arg2 = 1; + } + + g_at_result_iter_close_list(&iter); + +out: + if (two_arguments) + sprintf(buf, "AT+CGEREP=%u,%u", arg1, arg2); + else + sprintf(buf, "AT+CGEREP=%u", arg1); + + g_at_chat_send(gd->chat, buf, none_prefix, gprs_initialized, gprs, + NULL); +} + static void at_cgreg_test_cb(gboolean ok, GAtResult *result, gpointer user_data) { @@ -542,8 +762,8 @@ static void at_cgreg_test_cb(gboolean ok, GAtResult *result, gprs_initialized, gprs, NULL); break; default: - g_at_chat_send(gd->chat, "AT+CGEREP=2,1", none_prefix, - gprs_initialized, gprs, NULL); + g_at_chat_send(gd->chat, "AT+CGEREP=?", cgerep_prefix, + at_cgerep_test_cb, gprs, NULL); break; } @@ -622,6 +842,7 @@ static int at_gprs_probe(struct ofono_gprs *gprs, gd->chat = g_at_chat_clone(chat); gd->vendor = vendor; + gd->last_auto_context_id = -1; ofono_gprs_set_data(gprs, gd); @@ -647,6 +868,7 @@ static const struct ofono_gprs_driver driver = { .remove = at_gprs_remove, .set_attached = at_gprs_set_attached, .attached_status = at_gprs_registration_status, + .list_active_contexts = at_gprs_list_active_contexts, }; void at_gprs_init(void) diff --git a/ofono/drivers/atmodem/network-registration.c b/ofono/drivers/atmodem/network-registration.c index 2482dfc3a..cc702c2c2 100644 --- a/ofono/drivers/atmodem/network-registration.c +++ b/ofono/drivers/atmodem/network-registration.c @@ -41,6 +41,8 @@ #include "atmodem.h" #include "vendor.h" +#include "network-registration.h" + static const char *none_prefix[] = { NULL }; static const char *creg_prefix[] = { "+CREG:", NULL }; static const char *cops_prefix[] = { "+COPS:", NULL }; @@ -51,20 +53,6 @@ static const char *smoni_prefix[] = { "^SMONI:", NULL }; static const char *zpas_prefix[] = { "+ZPAS:", NULL }; static const char *option_tech_prefix[] = { "_OCTI:", "_OUWCTI:", NULL }; -struct netreg_data { - GAtChat *chat; - char mcc[OFONO_MAX_MCC_LENGTH + 1]; - char mnc[OFONO_MAX_MNC_LENGTH + 1]; - int signal_index; /* If strength is reported via CIND */ - int signal_min; /* min strength reported via CIND */ - int signal_max; /* max strength reported via CIND */ - int signal_invalid; /* invalid strength reported via CIND */ - int tech; - struct ofono_network_time time; - guint nitz_timeout; - unsigned int vendor; -}; - struct tech_query { int status; int lac; @@ -209,7 +197,7 @@ static void at_creg_cb(gboolean ok, GAtResult *result, gpointer user_data) ofono_netreg_status_cb_t cb = cbd->cb; int status, lac, ci, tech; struct ofono_error error; - struct netreg_data *nd = cbd->user; + struct at_netreg_data *nd = cbd->user; decode_at_error(&error, g_at_result_final_response(result)); @@ -250,7 +238,7 @@ static void zte_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; struct ofono_netreg *netreg = cbd->data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); if (ok) nd->tech = zte_parse_tech(result); @@ -262,7 +250,7 @@ static void option_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; struct ofono_netreg *netreg = cbd->data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); if (ok) nd->tech = option_parse_tech(result); @@ -270,11 +258,11 @@ static void option_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) nd->tech = -1; } -static void at_registration_status(struct ofono_netreg *netreg, +void at_registration_status(struct ofono_netreg *netreg, ofono_netreg_status_cb_t cb, void *data) { - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); cbd->user = nd; @@ -337,7 +325,7 @@ static void at_registration_status(struct ofono_netreg *netreg, static void cops_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; - struct netreg_data *nd = ofono_netreg_get_data(cbd->user); + struct at_netreg_data *nd = ofono_netreg_get_data(cbd->user); ofono_netreg_operator_cb_t cb = cbd->cb; struct ofono_network_operator op; GAtResultIter iter; @@ -398,7 +386,7 @@ static void cops_cb(gboolean ok, GAtResult *result, gpointer user_data) static void cops_numeric_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; - struct netreg_data *nd = ofono_netreg_get_data(cbd->user); + struct at_netreg_data *nd = ofono_netreg_get_data(cbd->user); ofono_netreg_operator_cb_t cb = cbd->cb; GAtResultIter iter; const char *str; @@ -450,10 +438,10 @@ static void cops_numeric_cb(gboolean ok, GAtResult *result, gpointer user_data) g_free(cbd); } -static void at_current_operator(struct ofono_netreg *netreg, +void at_current_operator(struct ofono_netreg *netreg, ofono_netreg_operator_cb_t cb, void *data) { - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); gboolean ok; @@ -589,10 +577,10 @@ static void cops_list_cb(gboolean ok, GAtResult *result, gpointer user_data) g_free(list); } -static void at_list_operators(struct ofono_netreg *netreg, +void at_list_operators(struct ofono_netreg *netreg, ofono_netreg_operator_list_cb_t cb, void *data) { - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); if (g_at_chat_send(nd->chat, "AT+COPS=?", cops_prefix, @@ -615,10 +603,10 @@ static void register_cb(gboolean ok, GAtResult *result, gpointer user_data) cb(&error, cbd->data); } -static void at_register_auto(struct ofono_netreg *netreg, +void at_register_auto(struct ofono_netreg *netreg, ofono_netreg_register_cb_t cb, void *data) { - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); if (g_at_chat_send(nd->chat, "AT+COPS=0", none_prefix, @@ -630,11 +618,11 @@ static void at_register_auto(struct ofono_netreg *netreg, CALLBACK_WITH_FAILURE(cb, data); } -static void at_register_manual(struct ofono_netreg *netreg, +void at_register_manual(struct ofono_netreg *netreg, const char *mcc, const char *mnc, ofono_netreg_register_cb_t cb, void *data) { - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); char buf[128]; @@ -723,7 +711,7 @@ static void ifx_xhomezr_notify(GAtResult *result, gpointer user_data) static void ifx_xreg_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); int state; const char *band; GAtResultIter iter; @@ -822,7 +810,7 @@ static void ifx_xcsq_notify(GAtResult *result, gpointer user_data) static void ciev_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); int strength, ind; GAtResultIter iter; @@ -851,7 +839,7 @@ static void ciev_notify(GAtResult *result, gpointer user_data) static void telit_ciev_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); const char *signal_identifier = "rssi"; const char *ind_str; int strength; @@ -882,7 +870,7 @@ static void telit_ciev_notify(GAtResult *result, gpointer user_data) static void gemalto_ciev_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); const char *signal_identifier = "rssi"; const char *ind_str; int strength; @@ -915,7 +903,7 @@ static void gemalto_ciev_notify(GAtResult *result, gpointer user_data) static void ctzv_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); const char *tz; GAtResultIter iter; @@ -937,7 +925,7 @@ static void ctzv_notify(GAtResult *result, gpointer user_data) static void tlts_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); int year, mon, mday, hour, min, sec; char tz[4]; const char *time; @@ -972,7 +960,7 @@ static void tlts_notify(GAtResult *result, gpointer user_data) static gboolean notify_time(gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); nd->nitz_timeout = 0; @@ -984,7 +972,7 @@ static gboolean notify_time(gpointer user_data) static void ifx_ctzv_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); int year, mon, mday, hour, min, sec; const char *tz, *time; GAtResultIter iter; @@ -1022,7 +1010,7 @@ static void ifx_ctzv_notify(GAtResult *result, gpointer user_data) static void ifx_ctzdst_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); int dst; GAtResultIter iter; @@ -1050,7 +1038,7 @@ static void cind_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; ofono_netreg_strength_cb_t cb = cbd->cb; - struct netreg_data *nd = cbd->user; + struct at_netreg_data *nd = cbd->user; int index; int strength; GAtResultIter iter; @@ -1104,7 +1092,7 @@ static void huawei_rssi_notify(GAtResult *result, gpointer user_data) static void huawei_mode_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; int mode, submode; @@ -1132,7 +1120,7 @@ static void huawei_mode_notify(GAtResult *result, gpointer user_data) static void huawei_hcsq_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *mode; @@ -1153,7 +1141,7 @@ static void huawei_hcsq_notify(GAtResult *result, gpointer user_data) static void huawei_nwtime_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); int year, mon, mday, hour, min, sec; char tz[4]; const char *date, *time, *dst; @@ -1228,10 +1216,10 @@ static void csq_cb(gboolean ok, GAtResult *result, gpointer user_data) cb(&error, strength, cbd->data); } -static void at_signal_strength(struct ofono_netreg *netreg, +void at_signal_strength(struct ofono_netreg *netreg, ofono_netreg_strength_cb_t cb, void *data) { - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); cbd->user = nd; @@ -1258,7 +1246,7 @@ static void at_signal_strength(struct ofono_netreg *netreg, static void mbm_etzv_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); int year, mon, mday, hour, min, sec; const char *tz, *time, *timestamp; GAtResultIter iter; @@ -1307,7 +1295,7 @@ static void mbm_etzv_notify(GAtResult *result, gpointer user_data) static void mbm_erinfo_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; int mode, gsm, umts; @@ -1359,7 +1347,7 @@ static void mbm_erinfo_notify(GAtResult *result, gpointer user_data) static void icera_nwstate_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *mccmnc, *tech, *state; int rssi; @@ -1427,7 +1415,7 @@ static int cnti_to_tech(const char *cnti) static void gobi_cnti_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *tech; int option; @@ -1452,7 +1440,7 @@ static void gobi_cnti_notify(GAtResult *result, gpointer user_data) static void nw_cnti_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *tech; int option; @@ -1478,7 +1466,7 @@ static void cnti_query_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct tech_query *tq = user_data; - struct netreg_data *nd = ofono_netreg_get_data(tq->netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(tq->netreg); ofono_netreg_status_notify(tq->netreg, tq->status, tq->lac, tq->ci, nd->tech); @@ -1518,7 +1506,7 @@ static void creg_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; int status, lac, ci, tech; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); struct tech_query *tq; if (at_util_parse_reg_unsolicited(result, "+CREG:", &status, @@ -1559,12 +1547,12 @@ static void creg_notify(GAtResult *result, gpointer user_data) option_query_tech_cb, tq, g_free) > 0) return; break; - case OFONO_VENDOR_GEMALTO: - if (g_at_chat_send(nd->chat, "AT^SMONI", - smoni_prefix, - gemalto_query_tech_cb, tq, g_free) > 0) - return; - break; + case OFONO_VENDOR_GEMALTO: + if (g_at_chat_send(nd->chat, "AT^SMONI", + smoni_prefix, + gemalto_query_tech_cb, tq, g_free) > 0) + return; + break; } g_free(tq); @@ -1587,7 +1575,7 @@ static void at_cmer_not_supported(struct ofono_netreg *netreg) static void at_cmer_set_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); if (!ok) { at_cmer_not_supported(netreg); @@ -1646,7 +1634,7 @@ static inline ofono_bool_t append_cmer_element(char *buf, int *len, int cap, } static ofono_bool_t build_cmer_string(char *buf, int *cmer_opts, - struct netreg_data *nd) + struct at_netreg_data *nd) { const char *ind; int len = sprintf(buf, "AT+CMER="); @@ -1655,8 +1643,11 @@ static ofono_bool_t build_cmer_string(char *buf, int *cmer_opts, DBG(""); switch (nd->vendor) { - case OFONO_VENDOR_UBLOX_TOBY_L2: - /* UBX-13002752 R33: TOBY L2 doesn't support mode 2 and 3 */ + case OFONO_VENDOR_UBLOX: + /* For all u-blox models, mode 3 is equivalent to mode 1; + * since some models do not support setting modes 2 nor 3 + * (see UBX-13002752), we prefer mode 1 for all models. + */ mode = "1"; break; default: @@ -1712,7 +1703,7 @@ static void at_cmer_query_cb(ofono_bool_t ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; int cmer_opts_cnt = 5; /* See 27.007 Section 8.10 */ int cmer_opts[cmer_opts_cnt]; @@ -1760,7 +1751,7 @@ static void at_cmer_query_cb(ofono_bool_t ok, GAtResult *result, static void cind_support_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *str; char *signal_identifier = "signal"; @@ -1867,7 +1858,7 @@ static void cind_support_cb(gboolean ok, GAtResult *result, gpointer user_data) static void at_creg_set_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); if (!ok) { ofono_error("Unable to initialize Network Registration"); @@ -2070,7 +2061,7 @@ static void at_creg_set_cb(gboolean ok, GAtResult *result, gpointer user_data) static void at_creg_test_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); gint range[2]; GAtResultIter iter; int creg1 = 0; @@ -2118,9 +2109,9 @@ static int at_netreg_probe(struct ofono_netreg *netreg, unsigned int vendor, void *data) { GAtChat *chat = data; - struct netreg_data *nd; + struct at_netreg_data *nd; - nd = g_new0(struct netreg_data, 1); + nd = g_new0(struct at_netreg_data, 1); nd->chat = g_at_chat_clone(chat); nd->vendor = vendor; @@ -2141,9 +2132,9 @@ static int at_netreg_probe(struct ofono_netreg *netreg, unsigned int vendor, return 0; } -static void at_netreg_remove(struct ofono_netreg *netreg) +void at_netreg_remove(struct ofono_netreg *netreg) { - struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); if (nd->nitz_timeout) g_source_remove(nd->nitz_timeout); diff --git a/ofono/drivers/atmodem/network-registration.h b/ofono/drivers/atmodem/network-registration.h new file mode 100644 index 000000000..1ec1b7241 --- /dev/null +++ b/ofono/drivers/atmodem/network-registration.h @@ -0,0 +1,30 @@ + +struct at_netreg_data { + GAtChat *chat; + char mcc[OFONO_MAX_MCC_LENGTH + 1]; + char mnc[OFONO_MAX_MNC_LENGTH + 1]; + int signal_index; /* If strength is reported via CIND */ + int signal_min; /* min strength reported via CIND */ + int signal_max; /* max strength reported via CIND */ + int signal_invalid; /* invalid strength reported via CIND */ + int tech; + struct ofono_network_time time; + guint nitz_timeout; + unsigned int vendor; +}; + +void at_registration_status(struct ofono_netreg *netreg, + ofono_netreg_status_cb_t cb, + void *data); +void at_current_operator(struct ofono_netreg *netreg, + ofono_netreg_operator_cb_t cb, void *data); +void at_list_operators(struct ofono_netreg *netreg, + ofono_netreg_operator_list_cb_t cb, void *data); +void at_register_auto(struct ofono_netreg *netreg, + ofono_netreg_register_cb_t cb, void *data); +void at_register_manual(struct ofono_netreg *netreg, + const char *mcc, const char *mnc, + ofono_netreg_register_cb_t cb, void *data); +void at_signal_strength(struct ofono_netreg *netreg, + ofono_netreg_strength_cb_t cb, void *data); +void at_netreg_remove(struct ofono_netreg *netreg); diff --git a/ofono/drivers/atmodem/sim.c b/ofono/drivers/atmodem/sim.c index 51f84818e..e750a1399 100644 --- a/ofono/drivers/atmodem/sim.c +++ b/ofono/drivers/atmodem/sim.c @@ -75,6 +75,7 @@ static const char *cuad_prefix[] = { "+CUAD:", NULL }; static const char *ccho_prefix[] = { "+CCHO:", NULL }; static const char *crla_prefix[] = { "+CRLA:", NULL }; static const char *cgla_prefix[] = { "+CGLA:", NULL }; +static const char *xcmscsc_prefix[] = { "+XCMSCSC:", NULL}; static const char *none_prefix[] = { NULL }; static void append_file_path(char *buf, const unsigned char *path, @@ -1160,6 +1161,7 @@ static void at_pin_retries_query(struct ofono_sim *sim, DBG(""); switch (sd->vendor) { + case OFONO_VENDOR_XMM: case OFONO_VENDOR_IFX: if (g_at_chat_send(sd->chat, "AT+XPINCNT", xpincnt_prefix, xpincnt_cb, cbd, g_free) > 0) @@ -1215,13 +1217,12 @@ static void at_pin_retries_query(struct ofono_sim *sim, at_qpinc_cb, cbd, g_free) > 0) return; break; - case OFONO_VENDOR_QUECTEL_M95: + case OFONO_VENDOR_QUECTEL_SERIAL: if (g_at_chat_send(sd->chat, "AT+QTRPIN", qtrpin_prefix, at_qtrpin_cb, cbd, g_free) > 0) return; break; case OFONO_VENDOR_UBLOX: - case OFONO_VENDOR_UBLOX_TOBY_L2: if (g_at_chat_send(sd->chat, "AT+UPINCNT", upincnt_prefix, upincnt_cb, cbd, g_free) > 0) return; @@ -1353,7 +1354,6 @@ static void at_pin_send_cb(gboolean ok, GAtResult *result, case OFONO_VENDOR_HUAWEI: case OFONO_VENDOR_SIMCOM: case OFONO_VENDOR_SIERRA: - case OFONO_VENDOR_QUECTEL_M95: /* * On ZTE modems, after pin is entered, SIM state is checked * by polling CPIN as their modem doesn't provide unsolicited @@ -1921,6 +1921,83 @@ static void at_logical_access(struct ofono_sim *sim, int session_id, CALLBACK_WITH_FAILURE(cb, NULL, 0, data); } +static void xcmscsc_query_cb(gboolean ok, GAtResult *result, gpointer user) +{ + struct ofono_sim *sim = user; + struct sim_data *sd = ofono_sim_get_data(sim); + GAtResultIter iter; + int active_slot; + + if (!ok) + goto done; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+XCMSCSC:")) + goto done; + + g_at_result_iter_skip_next(&iter); + g_at_result_iter_skip_next(&iter); + + g_at_result_iter_next_number(&iter, &active_slot); + + /* set active SIM slot */ + ofono_sim_set_active_card_slot(sim, active_slot + 1); + +done: + /* Query supported s */ + g_at_chat_send(sd->chat, "AT+CLCK=?", clck_prefix, + at_clck_query_cb, sim, NULL); +} + +static void at_xcmscsc_test_cb(gboolean ok, GAtResult *result, gpointer user) +{ + struct ofono_sim *sim = user; + struct sim_data *sd = ofono_sim_get_data(sim); + GAtResultIter iter; + int card_slot_count; + + if (!ok) + goto done; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+XCMSCSC:")) + goto done; + + g_at_result_iter_skip_next(&iter); + g_at_result_iter_skip_next(&iter); + + if (!g_at_result_iter_open_list(&iter)) + goto done; + + g_at_result_iter_skip_next(&iter); + + if (!g_at_result_iter_next_number(&iter, &card_slot_count)) + goto done; + + /* Set num slots */ + ofono_sim_set_card_slot_count(sim, card_slot_count + 1); + + /* + * enable reporting of MSIM remap status information + * and enable automatic acceptance of MSIM Remap + * acknowledgement + */ + g_at_chat_send(sd->chat, "AT+XCMSRS=2", none_prefix, + NULL, NULL, NULL); + + /* Query active card slot */ + g_at_chat_send(sd->chat, "AT+XCMSCSC?", xcmscsc_prefix, + xcmscsc_query_cb, sim, NULL); + return; + +done: + /* Query supported s */ + g_at_chat_send(sd->chat, "AT+CLCK=?", clck_prefix, + at_clck_query_cb, sim, NULL); +} + static int at_sim_probe(struct ofono_sim *sim, unsigned int vendor, void *data) { @@ -1939,6 +2016,10 @@ static int at_sim_probe(struct ofono_sim *sim, unsigned int vendor, if (at_clck_cpwd_fac[i]) sd->passwd_type_mask |= (1 << i); + if (sd->vendor == OFONO_VENDOR_XMM) + return g_at_chat_send(sd->chat, "AT+XCMSCSC=?", xcmscsc_prefix, + at_xcmscsc_test_cb, sim, NULL) ? 0 : -1; + /* Query supported s */ return g_at_chat_send(sd->chat, "AT+CLCK=?", clck_prefix, at_clck_query_cb, sim, NULL) ? 0 : -1; @@ -1958,6 +2039,46 @@ static void at_sim_remove(struct ofono_sim *sim) g_free(sd); } +static void xcmscsc_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sim_set_active_card_slot_cb_t cb = cbd->cb; + struct ofono_error error; + + decode_at_error(&error, g_at_result_final_response(result)); + + if (cb) + cb(&error, cbd->data); +} + +static void at_set_active_card_slot(struct ofono_sim *sim, unsigned int index, + ofono_sim_set_active_card_slot_cb_t cb, void *data) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data); + char cmd[43]; + + if (sd->vendor != OFONO_VENDOR_XMM) { + struct ofono_error error; + error.type = OFONO_ERROR_TYPE_CME; + error.error = 4; + + cb(&error, data); + return; + } + + /* Enable single SIM mode for indicated card slot id */ + snprintf(cmd, sizeof(cmd), "AT+XCMSCSC=1,0,%u,1", index); + + if (g_at_chat_send(sd->chat, cmd, none_prefix, xcmscsc_cb, + cbd, g_free) > 0) + return; + + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); +} + static const struct ofono_sim_driver driver = { .name = "atmodem", .probe = at_sim_probe, @@ -1983,7 +2104,8 @@ static const struct ofono_sim_driver driver = { .session_read_binary = at_session_read_binary, .session_read_record = at_session_read_record, .session_read_info = at_session_read_info, - .logical_access = at_logical_access + .logical_access = at_logical_access, + .set_active_card_slot = at_set_active_card_slot }; static const struct ofono_sim_driver driver_noef = { diff --git a/ofono/drivers/atmodem/sms.c b/ofono/drivers/atmodem/sms.c index 277d65175..ee3c40239 100644 --- a/ofono/drivers/atmodem/sms.c +++ b/ofono/drivers/atmodem/sms.c @@ -338,6 +338,9 @@ static inline void at_ack_delivery(struct ofono_sms *sms) case OFONO_VENDOR_GEMALTO: snprintf(buf, sizeof(buf), "AT+CNMA=1"); break; + case OFONO_VENDOR_QUECTEL_SERIAL: + snprintf(buf, sizeof(buf), "AT+CNMA"); + break; default: snprintf(buf, sizeof(buf), "AT+CNMA=1,%d\r%s", data->cnma_ack_pdu_len, @@ -840,6 +843,7 @@ static gboolean build_cnmi_string(char *buf, int *cnmi_opts, case OFONO_VENDOR_HUAWEI: case OFONO_VENDOR_ZTE: case OFONO_VENDOR_SIMCOM: + case OFONO_VENDOR_QUECTEL: /* MSM devices advertise support for mode 2, but return an * error if we attempt to actually use it. */ mode = "1"; @@ -1237,7 +1241,7 @@ static void at_csms_status_cb(gboolean ok, GAtResult *result, if (!g_at_result_iter_next_number(&iter, &mo)) goto out; - if (service == 1) + if (service == 1 || service == 128) data->cnma_enabled = TRUE; if (mt == 1 && mo == 1) @@ -1268,10 +1272,10 @@ static void at_csms_query_cb(gboolean ok, GAtResult *result, { struct ofono_sms *sms = user_data; struct sms_data *data = ofono_sms_get_data(sms); - gboolean cnma_supported = FALSE; GAtResultIter iter; int status_min, status_max; char buf[128]; + int csms = 0; if (!ok) return at_sms_not_supported(sms); @@ -1284,14 +1288,25 @@ static void at_csms_query_cb(gboolean ok, GAtResult *result, if (!g_at_result_iter_open_list(&iter)) goto out; - while (g_at_result_iter_next_range(&iter, &status_min, &status_max)) + switch (data->vendor) { + case OFONO_VENDOR_QUECTEL_SERIAL: + g_at_result_iter_next_number(&iter, &status_min); + g_at_result_iter_next_number(&iter, &status_max); if (status_min <= 1 && 1 <= status_max) - cnma_supported = TRUE; + csms = 128; + break; + default: + while (g_at_result_iter_next_range(&iter, &status_min, + &status_max)) + if (status_min <= 1 && 1 <= status_max) + csms = 1; + break; + } DBG("CSMS query parsed successfully"); out: - snprintf(buf, sizeof(buf), "AT+CSMS=%d", cnma_supported ? 1 : 0); + snprintf(buf, sizeof(buf), "AT+CSMS=%d", csms); g_at_chat_send(data->chat, buf, csms_prefix, at_csms_set_cb, sms, NULL); } diff --git a/ofono/drivers/atmodem/stk.c b/ofono/drivers/atmodem/stk.c index f0dc5c036..b2d208159 100644 --- a/ofono/drivers/atmodem/stk.c +++ b/ofono/drivers/atmodem/stk.c @@ -191,6 +191,19 @@ static gboolean at_stk_register(gpointer user) g_at_chat_register(sd->chat, "*HCMD:", phonesim_hcmd_notify, FALSE, stk, NULL); + if (sd->vendor == OFONO_VENDOR_XMM) { + /* enabling stk */ + g_at_chat_send(sd->chat, "AT+CFUN=6", none_prefix, + NULL, NULL, NULL); + /* Here ofono has missed stk menu proactive command + * that comes after sim initialization only. Doing a + * sim reset will enable the stk driver to get the + * missed +CUSATP notifications. + */ + g_at_chat_send(sd->chat, "AT+CFUN=27,1", none_prefix, + NULL, NULL, NULL); + } + ofono_stk_register(stk); return FALSE; diff --git a/ofono/drivers/atmodem/vendor.h b/ofono/drivers/atmodem/vendor.h index cd70c283b..d839d1e0f 100644 --- a/ofono/drivers/atmodem/vendor.h +++ b/ofono/drivers/atmodem/vendor.h @@ -44,9 +44,8 @@ enum ofono_vendor { OFONO_VENDOR_WAVECOM_Q2XXX, OFONO_VENDOR_ALCATEL, OFONO_VENDOR_QUECTEL, - OFONO_VENDOR_QUECTEL_M95, + OFONO_VENDOR_QUECTEL_SERIAL, OFONO_VENDOR_UBLOX, - OFONO_VENDOR_UBLOX_TOBY_L2, OFONO_VENDOR_XMM, OFONO_VENDOR_GEMALTO, }; diff --git a/ofono/drivers/atmodem/voicecall.c b/ofono/drivers/atmodem/voicecall.c index d55cf0089..7ab6567fa 100644 --- a/ofono/drivers/atmodem/voicecall.c +++ b/ofono/drivers/atmodem/voicecall.c @@ -264,14 +264,17 @@ static void clcc_poll_cb(gboolean ok, GAtResult *result, gpointer user_data) poll_clcc, vc); } +static void send_clcc(struct voicecall_data *vd, struct ofono_voicecall *vc) +{ + g_at_chat_send(vd->chat, "AT+CLCC", clcc_prefix, clcc_poll_cb, vc, NULL); +} + static gboolean poll_clcc(gpointer user_data) { struct ofono_voicecall *vc = user_data; struct voicecall_data *vd = ofono_voicecall_get_data(vc); - g_at_chat_send(vd->chat, "AT+CLCC", clcc_prefix, - clcc_poll_cb, vc, NULL); - + send_clcc(vd, vc); vd->clcc_source = 0; return FALSE; @@ -297,8 +300,7 @@ static void generic_cb(gboolean ok, GAtResult *result, gpointer user_data) } } - g_at_chat_send(vd->chat, "AT+CLCC", clcc_prefix, - clcc_poll_cb, req->vc, NULL); + send_clcc(vd, req->vc); /* We have to callback after we schedule a poll if required */ req->cb(&error, req->data); @@ -316,8 +318,7 @@ static void release_id_cb(gboolean ok, GAtResult *result, if (ok) vd->local_release = 1 << req->id; - g_at_chat_send(vd->chat, "AT+CLCC", clcc_prefix, - clcc_poll_cb, req->vc, NULL); + send_clcc(vd, req->vc); /* We have to callback after we schedule a poll if required */ req->cb(&error, req->data); @@ -962,8 +963,7 @@ static void no_carrier_notify(GAtResult *result, gpointer user_data) struct ofono_voicecall *vc = user_data; struct voicecall_data *vd = ofono_voicecall_get_data(vc); - g_at_chat_send(vd->chat, "AT+CLCC", clcc_prefix, - clcc_poll_cb, vc, NULL); + send_clcc(vd, vc); } static void no_answer_notify(GAtResult *result, gpointer user_data) @@ -971,8 +971,7 @@ static void no_answer_notify(GAtResult *result, gpointer user_data) struct ofono_voicecall *vc = user_data; struct voicecall_data *vd = ofono_voicecall_get_data(vc); - g_at_chat_send(vd->chat, "AT+CLCC", clcc_prefix, - clcc_poll_cb, vc, NULL); + send_clcc(vd, vc); } static void busy_notify(GAtResult *result, gpointer user_data) @@ -984,8 +983,7 @@ static void busy_notify(GAtResult *result, gpointer user_data) * or UDUB on the other side * TODO: Handle UDUB or other conditions somehow */ - g_at_chat_send(vd->chat, "AT+CLCC", clcc_prefix, - clcc_poll_cb, vc, NULL); + send_clcc(vd, vc); } static void cssi_notify(GAtResult *result, gpointer user_data) diff --git a/ofono/drivers/hsomodem/gprs-context.c b/ofono/drivers/hsomodem/gprs-context.c index 340092fae..ca622480c 100644 --- a/ofono/drivers/hsomodem/gprs-context.c +++ b/ofono/drivers/hsomodem/gprs-context.c @@ -150,7 +150,6 @@ static void hso_gprs_activate_primary(struct ofono_gprs_context *gc, struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); struct cb_data *cbd = cb_data_new(cb, data); char buf[AUTH_BUF_LENGTH]; - int len; /* IPv6 support not implemented */ if (ctx->proto != OFONO_GPRS_PROTO_IP) @@ -173,11 +172,8 @@ static void hso_gprs_activate_primary(struct ofono_gprs_context *gc, NULL, NULL, NULL) == 0) goto error; - len = snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\"", ctx->cid); - - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", - ctx->apn); + snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\",\"%s\"", + ctx->cid, ctx->apn); if (g_at_chat_send(gcd->chat, buf, none_prefix, hso_cgdcont_cb, cbd, g_free) > 0) diff --git a/ofono/drivers/huaweimodem/gprs-context.c b/ofono/drivers/huaweimodem/gprs-context.c index cae401c85..de07b055d 100644 --- a/ofono/drivers/huaweimodem/gprs-context.c +++ b/ofono/drivers/huaweimodem/gprs-context.c @@ -255,8 +255,7 @@ static void huawei_gprs_activate_primary(struct ofono_gprs_context *gc, { struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); struct cb_data *cbd = cb_data_new(cb, data); - char buf[64]; - int len; + char buf[136]; /* IPv6 support not implemented */ if (ctx->proto != OFONO_GPRS_PROTO_IP) @@ -265,14 +264,10 @@ static void huawei_gprs_activate_primary(struct ofono_gprs_context *gc, DBG("cid %u", ctx->cid); gcd->active_context = ctx->cid; - cbd->user = gc; - len = snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\"", ctx->cid); - - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", - ctx->apn); + snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\",\"%s\"", + ctx->cid, ctx->apn); if (g_at_chat_send(gcd->chat, buf, none_prefix, at_cgdcont_cb, cbd, g_free) > 0) diff --git a/ofono/drivers/iceramodem/gprs-context.c b/ofono/drivers/iceramodem/gprs-context.c index 395a9dc56..41d9d9bf5 100644 --- a/ofono/drivers/iceramodem/gprs-context.c +++ b/ofono/drivers/iceramodem/gprs-context.c @@ -332,9 +332,7 @@ static void icera_gprs_activate_primary(struct ofono_gprs_context *gc, break; } - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len - 3, - ",\"%s\"", ctx->apn); + snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", ctx->apn); if (g_at_chat_send(gcd->chat, buf, none_prefix, at_cgdcont_cb, cbd, g_free) > 0) diff --git a/ofono/drivers/ifxmodem/gprs-context.c b/ofono/drivers/ifxmodem/gprs-context.c index 18382ae60..27349dc19 100644 --- a/ofono/drivers/ifxmodem/gprs-context.c +++ b/ofono/drivers/ifxmodem/gprs-context.c @@ -44,6 +44,7 @@ #define TUN_DEV "/dev/net/tun" #define STATIC_IP_NETMASK "255.255.255.255" +#define IPV6_DEFAULT_PREFIX_LEN 8 static const char *none_prefix[] = { NULL }; static const char *xdns_prefix[] = { "+XDNS:", NULL }; @@ -260,6 +261,45 @@ static void address_cb(gboolean ok, GAtResult *result, gpointer user_data) failed_setup(gc, NULL, TRUE); } +static void cgdata_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct ofono_gprs_context *gc = user_data; + struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + + DBG("ok %d", ok); + + if (!ok) { + ofono_error("Failed to establish session"); + failed_setup(gc, result, TRUE); + return; + } + + CALLBACK_WITH_SUCCESS(gcd->cb, gcd->cb_data); +} + +static const char *get_datapath(struct ofono_modem *modem, + const char *interface) +{ + static char datapath[256]; + char n; + + if (!strcmp(interface, + ofono_modem_get_string(modem, "NetworkInterface"))) + n = '0'; + else if (!strcmp(interface, + ofono_modem_get_string(modem, "NetworkInterface2"))) + n = '1'; + else if (!strcmp(interface, + ofono_modem_get_string(modem, "NetworkInterface3"))) + n = '2'; + else + return NULL; + + snprintf(datapath, sizeof(datapath), "%s%c", + ofono_modem_get_string(modem, "DataPath"), n); + return datapath; +} + static void cgcontrdp_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_gprs_context *gc = user_data; @@ -269,8 +309,11 @@ static void cgcontrdp_cb(gboolean ok, GAtResult *result, gpointer user_data) const char *laddrnetmask = NULL; const char *gw = NULL; - const char *interface; const char *dns[3]; + const char *ctrlpath; + const char *datapath; + char buf[512]; + const char *interface; DBG("ok %d", ok); @@ -310,14 +353,45 @@ static void cgcontrdp_cb(gboolean ok, GAtResult *result, gpointer user_data) DBG("DNS: %s, %s\n", gcd->dns1, gcd->dns2); - if (!laddrnetmask || at_util_get_ipv4_address_and_netmask(laddrnetmask, + if (gw) + strncpy(gcd->gateway, gw, sizeof(gcd->gateway)); + + if (gcd->proto == OFONO_GPRS_PROTO_IP) { + if (!laddrnetmask || + at_util_get_ipv4_address_and_netmask(laddrnetmask, gcd->address, gcd->netmask) < 0) { - failed_setup(gc, NULL, TRUE); - return; + failed_setup(gc, NULL, TRUE); + return; + } + + ofono_gprs_context_set_ipv4_address(gc, gcd->address, TRUE); + + if (gcd->netmask[0]) + ofono_gprs_context_set_ipv4_netmask(gc, gcd->netmask); + + if (gcd->gateway[0]) + ofono_gprs_context_set_ipv4_gateway(gc, gcd->gateway); + + ofono_gprs_context_set_ipv4_dns_servers(gc, dns); } - if (gw) - strncpy(gcd->gateway, gw, sizeof(gcd->gateway)); + if (gcd->proto == OFONO_GPRS_PROTO_IPV6) { + if (!laddrnetmask || + at_util_get_ipv6_address_and_netmask(laddrnetmask, + gcd->address, gcd->netmask) < 0) { + failed_setup(gc, NULL, TRUE); + return; + } + + ofono_gprs_context_set_ipv6_address(gc, gcd->address); + + if (gcd->gateway[0]) + ofono_gprs_context_set_ipv6_gateway(gc, gcd->gateway); + + ofono_gprs_context_set_ipv6_dns_servers(gc, dns); + ofono_gprs_context_set_ipv6_prefix_length(gc, + IPV6_DEFAULT_PREFIX_LEN); + } gcd->state = STATE_ACTIVE; @@ -327,20 +401,21 @@ static void cgcontrdp_cb(gboolean ok, GAtResult *result, gpointer user_data) DBG("DNS2: %s\n", gcd->dns2); DBG("Gateway: %s\n", gcd->gateway); - interface = ofono_modem_get_string(modem, "NetworkInterface"); - - ofono_gprs_context_set_interface(gc, interface); - ofono_gprs_context_set_ipv4_address(gc, gcd->address, TRUE); - - if (gcd->netmask[0]) - ofono_gprs_context_set_ipv4_netmask(gc, gcd->netmask); + ctrlpath = ofono_modem_get_string(modem, "CtrlPath"); + interface = ofono_gprs_context_get_interface(gc); + datapath = get_datapath(modem, interface); - if (gcd->gateway[0]) - ofono_gprs_context_set_ipv4_gateway(gc, gcd->gateway); + snprintf(buf, sizeof(buf), "AT+XDATACHANNEL=1,1,\"%s\",\"%s\",0,%u", + ctrlpath, datapath, gcd->active_context); + g_at_chat_send(gcd->chat, buf, none_prefix, NULL, NULL, NULL); + snprintf(buf, sizeof(buf), "AT+CGDATA=\"M-RAW_IP\",%u", + gcd->active_context); - ofono_gprs_context_set_ipv4_dns_servers(gc, dns); + if (g_at_chat_send(gcd->chat, buf, none_prefix, cgdata_cb, + gc, NULL) > 0) + return; - CALLBACK_WITH_SUCCESS(gcd->cb, gcd->cb_data); + CALLBACK_WITH_FAILURE(gcd->cb, gcd->cb_data); } static void ifx_read_settings(struct ofono_gprs_context *gc) @@ -486,9 +561,7 @@ static void ifx_gprs_activate_primary(struct ofono_gprs_context *gc, break; } - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len - 3, - ",\"%s\"", ctx->apn); + snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", ctx->apn); if (g_at_chat_send(gcd->chat, buf, none_prefix, setup_cb, gc, NULL) > 0) diff --git a/ofono/drivers/isimodem/gprs-context.c b/ofono/drivers/isimodem/gprs-context.c index 5258cb17f..4076ed8b4 100644 --- a/ofono/drivers/isimodem/gprs-context.c +++ b/ofono/drivers/isimodem/gprs-context.c @@ -537,11 +537,15 @@ static void isi_gprs_activate_primary(struct ofono_gprs_context *gc, strncpy(cd->apn, ctx->apn, GPDS_MAX_APN_STRING_LENGTH); cd->apn[GPDS_MAX_APN_STRING_LENGTH] = '\0'; - strncpy(cd->username, ctx->username, GPDS_MAX_USERNAME_LENGTH); - cd->username[GPDS_MAX_USERNAME_LENGTH] = '\0'; - - strncpy(cd->password, ctx->password, GPDS_MAX_PASSWORD_LENGTH); - cd->username[GPDS_MAX_PASSWORD_LENGTH] = '\0'; + if (ctx->auth_method == OFONO_GPRS_AUTH_METHOD_NONE) { + memset(cd->username, 0, sizeof(cd->username)); + memset(cd->password, 0, sizeof(cd->password)); + } else { + strncpy(cd->username, ctx->username, GPDS_MAX_USERNAME_LENGTH); + cd->username[GPDS_MAX_USERNAME_LENGTH] = '\0'; + strncpy(cd->password, ctx->password, GPDS_MAX_PASSWORD_LENGTH); + cd->password[GPDS_MAX_PASSWORD_LENGTH] = '\0'; + } cd->pep = g_isi_pep_create(cd->idx, NULL, NULL); if (cd->pep == NULL) diff --git a/ofono/drivers/isimodem/voicecall.c b/ofono/drivers/isimodem/voicecall.c index 9a63f1004..3fb150f62 100644 --- a/ofono/drivers/isimodem/voicecall.c +++ b/ofono/drivers/isimodem/voicecall.c @@ -227,7 +227,7 @@ static void isi_call_any_address_sb_proc(struct isi_voicecall *ivc, call->addr_type = type | 0x80; call->presentation = pres; - strncpy(call->address, addr, sizeof(call->address)); + strncpy(call->address, addr, sizeof(call->address) - 1); g_free(addr); } diff --git a/ofono/drivers/mbimmodem/mbim-message.c b/ofono/drivers/mbimmodem/mbim-message.c index 781aff223..cd392c703 100644 --- a/ofono/drivers/mbimmodem/mbim-message.c +++ b/ofono/drivers/mbimmodem/mbim-message.c @@ -152,8 +152,8 @@ static bool _iter_copy_string(struct mbim_message_iter *iter, uint32_t offset, uint32_t len, char **out) { - uint8_t buf[len]; - uint8_t *dest = buf; + uint16_t buf[len / 2 + 1]; + uint8_t *dest = (uint8_t *) buf; uint32_t remaining = len; uint32_t iov_start = 0; uint32_t i = 0; @@ -195,7 +195,7 @@ static bool _iter_copy_string(struct mbim_message_iter *iter, /* Strings are in UTF16-LE, so convert to UTF16-CPU first if needed */ if (L_CPU_TO_LE16(0x8000) != 0x8000) { - uint16_t *le = (uint16_t *) buf; + uint16_t *le = buf; for (i = 0; i < len / 2; i++) le[i] = __builtin_bswap16(le[i]); diff --git a/ofono/drivers/mbmmodem/gprs-context.c b/ofono/drivers/mbmmodem/gprs-context.c index c48e7260f..f873e2835 100644 --- a/ofono/drivers/mbmmodem/gprs-context.c +++ b/ofono/drivers/mbmmodem/gprs-context.c @@ -367,7 +367,6 @@ static void mbm_gprs_activate_primary(struct ofono_gprs_context *gc, struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); struct cb_data *cbd = cb_data_new(cb, data); char buf[AUTH_BUF_LENGTH]; - int len; /* IPv6 support not implemented */ if (ctx->proto != OFONO_GPRS_PROTO_IP) @@ -376,14 +375,10 @@ static void mbm_gprs_activate_primary(struct ofono_gprs_context *gc, DBG("cid %u", ctx->cid); gcd->active_context = ctx->cid; - cbd->user = gc; - len = snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\"", ctx->cid); - - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", - ctx->apn); + snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\",\"%s\"", + ctx->cid, ctx->apn); if (g_at_chat_send(gcd->chat, buf, none_prefix, mbm_cgdcont_cb, cbd, g_free) == 0) diff --git a/ofono/drivers/qmimodem/devinfo.c b/ofono/drivers/qmimodem/devinfo.c index af976b772..365ff02b4 100644 --- a/ofono/drivers/qmimodem/devinfo.c +++ b/ofono/drivers/qmimodem/devinfo.c @@ -36,6 +36,7 @@ struct devinfo_data { struct qmi_service *dms; + bool device_is_3gpp; }; static void string_cb(struct qmi_result *result, void *user_data) @@ -116,7 +117,12 @@ static void qmi_query_revision(struct ofono_devinfo *devinfo, static void get_ids_cb(struct qmi_result *result, void *user_data) { struct cb_data *cbd = user_data; + struct ofono_devinfo *devinfo = cbd->user; + struct devinfo_data *data = ofono_devinfo_get_data(devinfo); ofono_devinfo_query_cb_t cb = cbd->cb; + char *esn; + char *imei; + char *meid; char *str; DBG(""); @@ -126,20 +132,28 @@ static void get_ids_cb(struct qmi_result *result, void *user_data) return; } - str = qmi_result_get_string(result, QMI_DMS_RESULT_ESN); - /* Telit qmi modems return a "0" string when ESN is not available. */ - if (!str || strcmp(str, "0") == 0) { - qmi_free(str); - str = qmi_result_get_string(result, QMI_DMS_RESULT_IMEI); - if (!str) { - CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); - return; - } - } + esn = qmi_result_get_string(result, QMI_DMS_RESULT_ESN); + imei = qmi_result_get_string(result, QMI_DMS_RESULT_IMEI); + meid = qmi_result_get_string(result, QMI_DMS_RESULT_MEID); - CALLBACK_WITH_SUCCESS(cb, str, cbd->data); + str = NULL; - qmi_free(str); + if (data->device_is_3gpp && imei && strcmp(imei, "0")) + str = imei; + else if (esn && strcmp(esn, "0")) + str = esn; + + if (str == NULL && meid && strcmp(meid, "0")) + str = meid; + + if (str) + CALLBACK_WITH_SUCCESS(cb, str, cbd->data); + else + CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); + + qmi_free(esn); + qmi_free(imei); + qmi_free(meid); } static void qmi_query_serial(struct ofono_devinfo *devinfo, @@ -150,6 +164,8 @@ static void qmi_query_serial(struct ofono_devinfo *devinfo, DBG(""); + cbd->user = devinfo; + if (qmi_service_send(data->dms, QMI_DMS_GET_IDS, NULL, get_ids_cb, cbd, g_free) > 0) return; @@ -159,6 +175,51 @@ static void qmi_query_serial(struct ofono_devinfo *devinfo, g_free(cbd); } +static void get_caps_cb(struct qmi_result *result, void *user_data) +{ + struct ofono_devinfo *devinfo = user_data; + struct devinfo_data *data = ofono_devinfo_get_data(devinfo); + const struct qmi_dms_device_caps *caps; + uint8_t i; + + DBG(""); + + if (qmi_result_set_error(result, NULL)) + goto error; + + caps = qmi_result_get(result, QMI_DMS_RESULT_DEVICE_CAPS, NULL); + if (caps == NULL) + goto error; + + data->device_is_3gpp = false; + + for (i = 0; i < caps->radio_if_count; i++) { + switch (caps->radio_if[i]) { + case QMI_DMS_RADIO_IF_GSM: + case QMI_DMS_RADIO_IF_UMTS: + case QMI_DMS_RADIO_IF_LTE: + data->device_is_3gpp = true; + break; + } + } + +error: + ofono_devinfo_register(devinfo); +} + +static void qmi_query_caps(struct ofono_devinfo *devinfo) +{ + struct devinfo_data *data = ofono_devinfo_get_data(devinfo); + + DBG(""); + + if (qmi_service_send(data->dms, QMI_DMS_GET_CAPS, NULL, + get_caps_cb, devinfo, NULL) > 0) + return; + + ofono_devinfo_register(devinfo); +} + static void create_dms_cb(struct qmi_service *service, void *user_data) { struct ofono_devinfo *devinfo = user_data; @@ -173,8 +234,9 @@ static void create_dms_cb(struct qmi_service *service, void *user_data) } data->dms = qmi_service_ref(service); + data->device_is_3gpp = false; - ofono_devinfo_register(devinfo); + qmi_query_caps(devinfo); } static int qmi_devinfo_probe(struct ofono_devinfo *devinfo, diff --git a/ofono/drivers/qmimodem/netmon.c b/ofono/drivers/qmimodem/netmon.c index 14a55632e..729879ce7 100644 --- a/ofono/drivers/qmimodem/netmon.c +++ b/ofono/drivers/qmimodem/netmon.c @@ -89,8 +89,8 @@ static void get_rssi_cb(struct qmi_result *result, void *user_data) /* RSSI */ rssi = qmi_result_get(result, 0x11, &len); - num = GUINT16_FROM_LE(rssi->count); if (rssi) { + num = GUINT16_FROM_LE(rssi->count); for (i = 0; i < num; i++) { DBG("RSSI: %hhu on RAT %hhd", rssi->info[i].rssi, @@ -126,8 +126,8 @@ static void get_rssi_cb(struct qmi_result *result, void *user_data) /* Bit error rate */ ber = qmi_result_get(result, 0x15, &len); - num = GUINT16_FROM_LE(ber->count); if (ber) { + num = GUINT16_FROM_LE(ber->count); for (i = 0; i < ber->count; i++) { DBG("Bit error rate: %hu on RAT %hhd", GUINT16_FROM_LE(ber->info[i].rate), diff --git a/ofono/drivers/qmimodem/network-registration.c b/ofono/drivers/qmimodem/network-registration.c index 1fccb5737..04f20c669 100644 --- a/ofono/drivers/qmimodem/network-registration.c +++ b/ofono/drivers/qmimodem/network-registration.c @@ -42,6 +42,8 @@ struct netreg_data { struct qmi_service *nas; struct ofono_network_operator operator; uint8_t current_rat; + int lac; + int cellid; bool is_roaming; }; @@ -166,6 +168,31 @@ static bool extract_ss_info(struct qmi_result *result, int *status, return true; } +static int remember_ss_info(struct netreg_data *data, int status, int lac, + int cellid, enum roaming_status roaming) +{ + if (roaming == ROAMING_STATUS_ON) + data->is_roaming = true; + else if (roaming == ROAMING_STATUS_OFF) + data->is_roaming = false; + + if (status == QMI_NAS_REGISTRATION_STATE_REGISTERED) { + if (lac >= 0) + data->lac = lac; + if (cellid >= 0) + data->cellid = cellid; + } else { + data->lac = -1; + data->cellid = -1; + } + + if (status == QMI_NAS_REGISTRATION_STATE_REGISTERED && + data->is_roaming) + status = NETWORK_REGISTRATION_STATUS_ROAMING; + + return status; +} + static void ss_info_notify(struct qmi_result *result, void *user_data) { struct ofono_netreg *netreg = user_data; @@ -183,16 +210,10 @@ static void ss_info_notify(struct qmi_result *result, void *user_data) &data->operator)) return; - if (roaming == ROAMING_STATUS_ON) - data->is_roaming = true; - else if (roaming == ROAMING_STATUS_OFF) - data->is_roaming = false; - - if (status == QMI_NAS_REGISTRATION_STATE_REGISTERED && - data->is_roaming) - status = NETWORK_REGISTRATION_STATUS_ROAMING; + status = remember_ss_info(data, status, lac, cellid, roaming); - ofono_netreg_status_notify(netreg, status, lac, cellid, tech); + ofono_netreg_status_notify(netreg, status, data->lac, data->cellid, + tech); } static void get_ss_info_cb(struct qmi_result *result, void *user_data) @@ -216,16 +237,10 @@ static void get_ss_info_cb(struct qmi_result *result, void *user_data) return; } - if (roaming == ROAMING_STATUS_ON) - data->is_roaming = true; - else if (roaming == ROAMING_STATUS_OFF) - data->is_roaming = false; - - if (status == QMI_NAS_REGISTRATION_STATE_REGISTERED && - data->is_roaming) - status = NETWORK_REGISTRATION_STATUS_ROAMING; + status = remember_ss_info(data, status, lac, cellid, roaming); - CALLBACK_WITH_SUCCESS(cb, status, lac, cellid, tech, cbd->data); + CALLBACK_WITH_SUCCESS(cb, status, data->lac, data->cellid, tech, + cbd->data); } static void qmi_registration_status(struct ofono_netreg *netreg, @@ -613,6 +628,8 @@ static int qmi_netreg_probe(struct ofono_netreg *netreg, data->current_rat = QMI_NAS_NETWORK_RAT_NO_CHANGE; data->is_roaming = false; + data->lac = -1; + data->cellid = -1; ofono_netreg_set_data(netreg, data); diff --git a/ofono/drivers/qmimodem/qmi.c b/ofono/drivers/qmimodem/qmi.c index 19ec13038..4cd1530f4 100644 --- a/ofono/drivers/qmimodem/qmi.c +++ b/ofono/drivers/qmimodem/qmi.c @@ -477,7 +477,8 @@ static const char *__error_to_string(uint16_t error) return NULL; } -int qmi_error_to_ofono_cme(int qmi_error) { +int qmi_error_to_ofono_cme(int qmi_error) +{ switch (qmi_error) { case 0x0019: return 4; /* Not Supported */ @@ -1209,10 +1210,10 @@ static void discover_callback(uint16_t message, uint16_t length, if (name) __debug_device(device, "found service [%s %d.%d]", - name, major, minor); + name, major, minor); else __debug_device(device, "found service [%d %d.%d]", - type, major, minor); + type, major, minor); if (type == QMI_SERVICE_CONTROL) { device->control_major = major; diff --git a/ofono/drivers/qmimodem/sim-legacy.c b/ofono/drivers/qmimodem/sim-legacy.c index af8576935..601e721d1 100644 --- a/ofono/drivers/qmimodem/sim-legacy.c +++ b/ofono/drivers/qmimodem/sim-legacy.c @@ -83,13 +83,13 @@ static void get_iccid_cb(struct qmi_result *result, void *user_data) len = strlen(str); if (len > 20) { + qmi_free(str); CALLBACK_WITH_FAILURE(cb, NULL, 0, cbd->data); return; } sim_encode_bcd_number(str, iccid); iccid_len = len / 2; - qmi_free(str); CALLBACK_WITH_SUCCESS(cb, iccid, iccid_len, cbd->data); @@ -250,6 +250,7 @@ static void process_uim_state(struct ofono_sim *sim, uint8_t state) switch (state) { case QMI_DMS_UIM_STATE_INIT_COMPLETE: ofono_sim_inserted_notify(sim, TRUE); + ofono_sim_initialized_notify(sim); break; case QMI_DMS_UIM_STATE_INIT_FAILED: case QMI_DMS_UIM_STATE_NOT_PRESENT: diff --git a/ofono/drivers/qmimodem/sim.c b/ofono/drivers/qmimodem/sim.c index 8edabb806..b96f8efb9 100644 --- a/ofono/drivers/qmimodem/sim.c +++ b/ofono/drivers/qmimodem/sim.c @@ -524,7 +524,7 @@ static bool get_card_status(const struct qmi_uim_slot_info *slot, return need_retry; } -static enum get_card_status_result handle_get_card_status_result( +static enum get_card_status_result handle_get_card_status_data( struct qmi_result *result, struct sim_status *sim_stat) { const void *ptr; @@ -533,9 +533,6 @@ static enum get_card_status_result handle_get_card_status_result( uint8_t i; enum get_card_status_result res = GET_CARD_STATUS_RESULT_ERROR; - if (qmi_result_set_error(result, NULL)) - goto done; - ptr = qmi_result_get(result, QMI_UIM_RESULT_CARD_STATUS, &len); if (!ptr) goto done; @@ -578,6 +575,15 @@ static enum get_card_status_result handle_get_card_status_result( return res; } +static enum get_card_status_result handle_get_card_status_result( + struct qmi_result *result, struct sim_status *sim_stat) +{ + if (qmi_result_set_error(result, NULL)) + return GET_CARD_STATUS_RESULT_ERROR; + + return handle_get_card_status_data(result, sim_stat); +} + static gboolean query_passwd_state_retry(gpointer userdata) { struct cb_data *cbd = userdata; @@ -789,6 +795,34 @@ static void get_card_status_cb(struct qmi_result *result, void *user_data) case 0x00: /* Absent */ case 0x02: /* Error */ break; + case 0x01: /* Present */ + ofono_sim_inserted_notify(sim, TRUE); + ofono_sim_initialized_notify(sim); + break; + } +} + +static void card_status_notify(struct qmi_result *result, void *user_data) +{ + struct ofono_sim *sim = user_data; + struct sim_data *data = ofono_sim_get_data(sim); + struct sim_status sim_stat; + + DBG(""); + + if (handle_get_card_status_data(result, &sim_stat) != + GET_CARD_STATUS_RESULT_OK) { + data->app_type = 0; /* Unknown */ + sim_stat.card_state = 0x00; /* Absent */ + } else { + data->app_type = sim_stat.app_type; + } + + switch (sim_stat.card_state) { + case 0x00: /* Absent */ + case 0x02: /* Error */ + ofono_sim_inserted_notify(sim, FALSE); + break; case 0x01: /* Present */ ofono_sim_inserted_notify(sim, TRUE); break; @@ -810,6 +844,9 @@ static void event_registration_cb(struct qmi_result *result, void *user_data) goto error; DBG("event mask 0x%04x", data->event_mask); + if (data->event_mask & 0x0001) + qmi_service_register(data->uim, QMI_UIM_GET_CARD_STATUS_EVENT, + card_status_notify, sim, NULL); if (qmi_service_send(data->uim, QMI_UIM_GET_CARD_STATUS, NULL, get_card_status_cb, sim, NULL) > 0) diff --git a/ofono/drivers/qmimodem/sms.c b/ofono/drivers/qmimodem/sms.c index 1e9303966..2e9624297 100644 --- a/ofono/drivers/qmimodem/sms.c +++ b/ofono/drivers/qmimodem/sms.c @@ -39,8 +39,17 @@ struct sms_data { struct qmi_service *wms; uint16_t major; uint16_t minor; + struct qmi_wms_read_msg_id rd_msg_id; + struct qmi_wms_result_msg_list *msg_list; + uint32_t rd_msg_num; + uint8_t msg_mode; + bool msg_mode_all; + bool msg_list_chk; }; +static void get_msg_list(struct ofono_sms *sms); +static void raw_read(struct ofono_sms *sms, uint8_t type, uint32_t ndx); + static void get_smsc_addr_cb(struct qmi_result *result, void *user_data) { struct cb_data *cbd = user_data; @@ -334,21 +343,95 @@ static void qmi_bearer_set(struct ofono_sms *sms, int bearer, g_free(cbd); } +static void delete_msg_cb(struct qmi_result *result, void *user_data) +{ + struct ofono_sms *sms = user_data; + struct sms_data *data = ofono_sms_get_data(sms); + uint16_t err; + + DBG(""); + + if (qmi_result_set_error(result, &err)) + DBG("Err: delete %d - %s", err, qmi_result_get_error(result)); + + /* + * Continue processing msg list. If error occurred, something + * serious happened, then don't bother. + */ + if (data->msg_list && data->msg_list_chk) { + uint32_t msg = ++data->rd_msg_num; + + /* + * Get another msg. If list is empty check for more. Once query + * returns empty, rely on event indication to get new msgs. + */ + if (msg < data->msg_list->cnt) + raw_read(sms, data->msg_list->msg[msg].type, + GUINT32_FROM_LE(data->msg_list->msg[msg].ndx)); + else + get_msg_list(sms); + } +} + +static void delete_msg(struct ofono_sms *sms, uint8_t tag) +{ + struct sms_data *data = ofono_sms_get_data(sms); + struct qmi_param *param; + qmi_result_func_t func = NULL; + + DBG(""); + + param = qmi_param_new(); + if (param == NULL) + goto done; + + qmi_param_append_uint8(param, QMI_WMS_PARAM_DEL_STORE, + QMI_WMS_STORAGE_TYPE_NV); + + if (tag == QMI_WMS_MT_UNDEFINE) { + DBG("delete read msg type %d ndx %d", data->rd_msg_id.type, + data->rd_msg_id.ndx); + + /* delete 1 msg */ + qmi_param_append_uint32(param, QMI_WMS_PARAM_DEL_NDX, + data->rd_msg_id.ndx); + func = delete_msg_cb; + } else { + DBG("delete msg tag %d mode %d", tag, data->msg_mode); + + /* delete all msgs from 1 tag type */ + qmi_param_append_uint8(param, QMI_WMS_PARAM_DEL_TYPE, tag); + } + + qmi_param_append_uint8(param, QMI_WMS_PARAM_DEL_MODE, data->msg_mode); + + if (qmi_service_send(data->wms, QMI_WMS_DELETE, param, + func, sms, NULL) > 0) + return; + + qmi_param_free(param); + +done: + data->msg_list_chk = false; +} + static void raw_read_cb(struct qmi_result *result, void *user_data) { struct ofono_sms *sms = user_data; - const struct qmi_wms_raw_message* msg; - uint16_t len; - uint16_t error; + struct sms_data *data = ofono_sms_get_data(sms); + const struct qmi_wms_raw_message *msg; + uint16_t err; - if (qmi_result_set_error(result, &error)) { - DBG("Raw read error: %d (%s)", error, - qmi_result_get_error(result)); + DBG(""); + + if (qmi_result_set_error(result, &err)) { + DBG("Err: read %d - %s", err, qmi_result_get_error(result)); + data->msg_list_chk = false; return; } /* Raw message data */ - msg = qmi_result_get(result, 0x01, &len); + msg = qmi_result_get(result, QMI_WMS_RESULT_READ_MSG, NULL); if (msg) { uint16_t plen; uint16_t tpdu_len; @@ -357,9 +440,174 @@ static void raw_read_cb(struct qmi_result *result, void *user_data) tpdu_len = plen - msg->msg_data[0] - 1; ofono_sms_deliver_notify(sms, msg->msg_data, plen, tpdu_len); + } else + DBG("Err: no data in type %d ndx %d", data->rd_msg_id.type, + data->rd_msg_id.ndx); + + /* delete read msg */ + delete_msg(sms, QMI_WMS_MT_UNDEFINE); +} + +static void raw_read(struct ofono_sms *sms, uint8_t type, uint32_t ndx) +{ + struct sms_data *data = ofono_sms_get_data(sms); + struct qmi_param *param; + + DBG(""); + + param = qmi_param_new(); + if (param == NULL) + goto done; + + data->rd_msg_id.type = type; + data->rd_msg_id.ndx = ndx; + + DBG("read type %d ndx %d", data->rd_msg_id.type, data->rd_msg_id.ndx); + + qmi_param_append(param, QMI_WMS_PARAM_READ_MSG, + sizeof(data->rd_msg_id), &data->rd_msg_id); + qmi_param_append_uint8(param, QMI_WMS_PARAM_READ_MODE, data->msg_mode); + + if (qmi_service_send(data->wms, QMI_WMS_RAW_READ, param, + raw_read_cb, sms, NULL) > 0) + return; + + qmi_param_free(param); + +done: + data->msg_list_chk = false; +} + +static void get_msg_list_cb(struct qmi_result *result, void *user_data) +{ + struct ofono_sms *sms = user_data; + struct sms_data *data = ofono_sms_get_data(sms); + const struct qmi_wms_result_msg_list *list; + uint32_t cnt = 0; + uint16_t tmp; + + DBG(""); + + if (qmi_result_set_error(result, &tmp)) { + DBG("Err: get msg list mode=%d %d=%s", data->msg_mode, tmp, + qmi_result_get_error(result)); + goto done; + } + + list = qmi_result_get(result, QMI_WMS_RESULT_MSG_LIST, NULL); + if (list == NULL) { + DBG("Err: get msg list empty"); + goto done; + } + + cnt = GUINT32_FROM_LE(list->cnt); + DBG("msgs found %d", cnt); + + for (tmp = 0; tmp < cnt; tmp++) { + DBG("unread type %d ndx %d", list->msg[tmp].type, + GUINT32_FROM_LE(list->msg[tmp].ndx)); + } + + /* free list from last time */ + if (data->msg_list) { + g_free(data->msg_list); + data->msg_list = NULL; + } + + /* save list and get 1st msg */ + if (cnt) { + int msg_size = cnt * sizeof(list->msg[0]); + + data->msg_list = g_try_malloc0(sizeof(list->cnt) + msg_size); + if (data->msg_list == NULL) + goto done; + + data->msg_list->cnt = cnt; + memcpy(data->msg_list->msg, list->msg, msg_size); + + data->rd_msg_num = 0; + raw_read(sms, data->msg_list->msg[0].type, + GUINT32_FROM_LE(data->msg_list->msg[0].ndx)); + return; + } + +done: + data->msg_list_chk = false; + + /* if both protocols supported, check the other */ + if (data->msg_mode_all) { + data->msg_mode_all = false; + data->msg_mode = QMI_WMS_MESSAGE_MODE_GSMWCDMA; + get_msg_list(sms); + } +} + +static void get_msg_list(struct ofono_sms *sms) +{ + struct sms_data *data = ofono_sms_get_data(sms); + struct qmi_param *param; + + DBG(""); + + param = qmi_param_new(); + if (param == NULL) + return; + + data->msg_list_chk = true; + + /* query NOT_READ msg list */ + qmi_param_append_uint8(param, QMI_WMS_PARAM_STORAGE_TYPE, + QMI_WMS_STORAGE_TYPE_NV); + qmi_param_append_uint8(param, QMI_WMS_PARAM_TAG_TYPE, + QMI_WMS_MT_NOT_READ); + qmi_param_append_uint8(param, QMI_WMS_PARAM_MESSAGE_MODE, + data->msg_mode); + + if (qmi_service_send(data->wms, QMI_WMS_GET_MSG_LIST, param, + get_msg_list_cb, sms, NULL) > 0) + return; + + data->msg_list_chk = false; + qmi_param_free(param); +} + +static void get_msg_protocol_cb(struct qmi_result *result, void *user_data) +{ + struct ofono_sms *sms = user_data; + struct sms_data *data = ofono_sms_get_data(sms); + uint16_t err; + + DBG(""); + + if (qmi_result_set_error(result, &err) && + (err != QMI_ERR_OP_DEVICE_UNSUPPORTED)) { + DBG("Err: protocol %d - %s", err, qmi_result_get_error(result)); + return; + } + + if (err != QMI_ERR_OP_DEVICE_UNSUPPORTED) { + /* modem supports only 1 protocol */ + qmi_result_get_uint8(result, QMI_WMS_PARAM_PROTOCOL, + &data->msg_mode); } else { - DBG("No message data available at requested position"); + /* check both, start with 1 then switch to other */ + DBG("device supports CDMA and WCDMA msg protocol"); + data->msg_mode_all = true; + data->msg_mode = QMI_WMS_MESSAGE_MODE_CDMA; } + + /* check for messages */ + get_msg_list(sms); +} + +static void get_msg_protocol(struct ofono_sms *sms) +{ + struct sms_data *data = ofono_sms_get_data(sms); + + DBG(""); + + qmi_service_send(data->wms, QMI_WMS_GET_MSG_PROTOCOL, NULL, + get_msg_protocol_cb, sms, NULL); } static void event_notify(struct qmi_result *result, void *user_data) @@ -367,66 +615,78 @@ static void event_notify(struct qmi_result *result, void *user_data) struct ofono_sms *sms = user_data; struct sms_data *data = ofono_sms_get_data(sms); const struct qmi_wms_result_new_msg_notify *notify; - const struct qmi_wms_result_message *message; - uint16_t len; DBG(""); - notify = qmi_result_get(result, QMI_WMS_RESULT_NEW_MSG_NOTIFY, &len); + /* + * The 2 types of MT message TLVs are mutually exclusive, depending on + * how the route action is configured. If action is store and notify, + * then the MT message TLV is sent. If action is transfer only or + * transfer and ack, then the transfer route MT message TLV is sent. + */ + notify = qmi_result_get(result, QMI_WMS_RESULT_NEW_MSG_NOTIFY, NULL); if (notify) { - DBG("storage type %d index %d", notify->storage_type, - GUINT32_FROM_LE(notify->storage_index)); - } + /* route is store and notify */ + if (!qmi_result_get_uint8(result, QMI_WMS_RESULT_MSG_MODE, + &data->msg_mode)) + DBG("msg mode not found, use mode %d", data->msg_mode); + + DBG("msg type %d ndx %d mode %d", notify->storage_type, + GUINT32_FROM_LE(notify->storage_index), data->msg_mode); + + /* don't read if list is being processed, get this msg later */ + if (!data->msg_list_chk) + raw_read(sms, notify->storage_type, + GUINT32_FROM_LE(notify->storage_index)); + } else { + /* route is either transfer only or transfer and ACK */ + const struct qmi_wms_result_message *message; - message = qmi_result_get(result, QMI_WMS_RESULT_MESSAGE, &len); - if (message) { - uint16_t plen; + message = qmi_result_get(result, QMI_WMS_RESULT_MESSAGE, NULL); + if (message) { + uint16_t plen; - plen = GUINT16_FROM_LE(message->msg_length); + plen = GUINT16_FROM_LE(message->msg_length); - DBG("ack_required %d transaction id %u", message->ack_required, + DBG("ack_required %d transaction id %u", + message->ack_required, GUINT32_FROM_LE(message->transaction_id)); - DBG("msg format %d PDU length %d", message->msg_format, plen); + DBG("msg format %d PDU length %d", + message->msg_format, plen); - ofono_sms_deliver_notify(sms, message->msg_data, plen, plen); - } else { - /* The Quectel EC21, at least, does not provide the - * message data in the event notification, so a 'raw read' - * needs to be issued in order to query the message itself - */ - struct qmi_param *param; - - param = qmi_param_new(); - if (!param) - return; - - /* Message memory storage ID */ - qmi_param_append(param, 0x01, sizeof(*notify), notify); - /* The 'message mode' parameter is documented as optional, - * but the Quectel EC21 errors out with error 17 (missing - * argument) if it is not provided... we default to 3GPP - * here because that's what works for me and it's not clear - * how to actually query what this should be otherwise... - */ - /* Message mode */ - qmi_param_append_uint8(param, 0x10, - QMI_WMS_MESSAGE_MODE_GSMWCDMA); - - if (qmi_service_send(data->wms, QMI_WMS_RAW_READ, param, - raw_read_cb, sms, NULL) > 0) - return; - - qmi_param_free(param); + ofono_sms_deliver_notify(sms, message->msg_data, + plen, plen); + } } } static void set_routes_cb(struct qmi_result *result, void *user_data) { struct ofono_sms *sms = user_data; + struct sms_data *data = ofono_sms_get_data(sms); DBG(""); ofono_sms_register(sms); + + /* + * Modem storage is limited. As a fail safe, delete processed messages + * to free device memory to prevent blockage of new messages. + */ + data->msg_mode = QMI_WMS_MESSAGE_MODE_CDMA; + delete_msg(sms, QMI_WMS_MT_READ); + delete_msg(sms, QMI_WMS_MO_SENT); + data->msg_mode = QMI_WMS_MESSAGE_MODE_GSMWCDMA; + delete_msg(sms, QMI_WMS_MT_READ); + delete_msg(sms, QMI_WMS_MO_SENT); + + /* + * Subsystem initialized, now start process to check for unread + * messages. First, query msg protocol/mode. If modem supports both + * modes, then check messages for both modes since there's no way to + * query which mode is active. + */ + get_msg_protocol(sms); } static void get_routes_cb(struct qmi_result *result, void *user_data) @@ -468,8 +728,8 @@ static void get_routes_cb(struct qmi_result *result, void *user_data) new_list->count = GUINT16_TO_LE(1); new_list->route[0].msg_type = QMI_WMS_MSG_TYPE_P2P; new_list->route[0].msg_class = QMI_WMS_MSG_CLASS_NONE; - new_list->route[0].storage_type = QMI_WMS_STORAGE_TYPE_NONE; - new_list->route[0].action = QMI_WMS_ACTION_TRANSFER_AND_ACK; + new_list->route[0].storage_type = QMI_WMS_STORAGE_TYPE_NV; + new_list->route[0].action = QMI_WMS_ACTION_STORE_AND_NOTIFY; param = qmi_param_new(); if (!param) @@ -524,6 +784,9 @@ static void create_wms_cb(struct qmi_service *service, void *user_data) data->wms = qmi_service_ref(service); + memset(&data->rd_msg_id, 0, sizeof(data->rd_msg_id)); + data->msg_mode = QMI_WMS_MESSAGE_MODE_GSMWCDMA; + qmi_service_register(data->wms, QMI_WMS_EVENT, event_notify, sms, NULL); @@ -568,6 +831,9 @@ static void qmi_sms_remove(struct ofono_sms *sms) qmi_service_unref(data->wms); + if (data->msg_list) + g_free(data->msg_list); + g_free(data); } diff --git a/ofono/drivers/qmimodem/uim.h b/ofono/drivers/qmimodem/uim.h index cd10e684b..9003aba30 100644 --- a/ofono/drivers/qmimodem/uim.h +++ b/ofono/drivers/qmimodem/uim.h @@ -29,7 +29,7 @@ #define QMI_UIM_EVENT_REGISTRATION 46 /* Register for indications */ #define QMI_UIM_GET_CARD_STATUS 47 /* Get card status */ - +#define QMI_UIM_GET_CARD_STATUS_EVENT 50 /* Card status indication */ /* Register for indications */ #define QMI_UIM_PARAM_EVENT_MASK 0x01 /* uint32 */ diff --git a/ofono/drivers/qmimodem/wms.h b/ofono/drivers/qmimodem/wms.h index 7e18ec9d4..f53fc1bdf 100644 --- a/ofono/drivers/qmimodem/wms.h +++ b/ofono/drivers/qmimodem/wms.h @@ -25,8 +25,9 @@ #define QMI_WMS_RAW_SEND 32 /* Send a raw message */ -#define QMI_WMS_RAW_READ 34 /* Read raw message from storage*/ - +#define QMI_WMS_RAW_READ 34 /* Read raw message from storage */ +#define QMI_WMS_DELETE 36 /* Delete message */ +#define QMI_WMS_GET_MSG_PROTOCOL 48 /* Get message protocol */ #define QMI_WMS_GET_MSG_LIST 49 /* Get list of messages from the device */ #define QMI_WMS_SET_ROUTES 50 /* Set routes for message memory storage */ #define QMI_WMS_GET_ROUTES 51 /* Get routes for message memory storage */ @@ -45,6 +46,17 @@ struct qmi_wms_result_new_msg_notify { uint32_t storage_index; } __attribute__((__packed__)); +#define QMI_WMS_RESULT_MESSAGE 0x11 +struct qmi_wms_result_message { + uint8_t ack_required; /* bool */ + uint32_t transaction_id; + uint8_t msg_format; + uint16_t msg_length; + uint8_t msg_data[0]; +} __attribute__((__packed__)); + +#define QMI_WMS_RESULT_MSG_MODE 0x12 + /* Set new message conditions */ #define QMI_WMS_PARAM_NEW_MSG_REPORT 0x10 /* bool */ @@ -57,23 +69,59 @@ struct qmi_wms_param_message { } __attribute__((__packed__)); #define QMI_WMS_RESULT_MESSAGE_ID 0x01 /* uint16 */ +/* Read a raw message */ +#define QMI_WMS_PARAM_READ_MSG 0x01 +struct qmi_wms_read_msg_id { + uint8_t type; + uint32_t ndx; +} __attribute__((__packed__)); + +#define QMI_WMS_PARAM_READ_MODE 0x10 + +#define QMI_WMS_RESULT_READ_MSG 0x01 +struct qmi_wms_raw_message { + uint8_t msg_tag; + uint8_t msg_format; + uint16_t msg_length; + uint8_t msg_data[0]; +} __attribute__((__packed__)); + +/* Delete messages */ +#define QMI_WMS_PARAM_DEL_STORE 0x01 +#define QMI_WMS_PARAM_DEL_NDX 0x10 +#define QMI_WMS_PARAM_DEL_TYPE 0x11 +#define QMI_WMS_PARAM_DEL_MODE 0x12 + +/* Get message protocol */ +#define QMI_WMS_PARAM_PROTOCOL 0x01 + /* Get list of messages from the device */ #define QMI_WMS_PARAM_STORAGE_TYPE 0x01 /* uint8 */ +#define QMI_WMS_PARAM_TAG_TYPE 0x10 #define QMI_WMS_PARAM_MESSAGE_MODE 0x11 /* uint8 */ +#define QMI_WMS_RESULT_MSG_LIST 0x01 +struct qmi_wms_result_msg_list { + uint32_t cnt; + struct { + uint32_t ndx; + uint8_t type; + } __attribute__((__packed__)) msg[0]; +} __attribute__((__packed__)); + #define QMI_WMS_STORAGE_TYPE_UIM 0 #define QMI_WMS_STORAGE_TYPE_NV 1 #define QMI_WMS_STORAGE_TYPE_UNKNOWN 2 #define QMI_WMS_STORAGE_TYPE_NONE 255 -#define QMI_WMS_MESSAGE_MODE_GSMWCDMA 1 +#define QMI_WMS_MT_READ 0x00 +#define QMI_WMS_MT_NOT_READ 0x01 +#define QMI_WMS_MO_SENT 0x02 +#define QMI_WMS_MO_NOT_SENT 0x03 +#define QMI_WMS_MT_UNDEFINE 0xff -struct qmi_wms_raw_message { - uint8_t msg_tag; - uint8_t msg_format; - uint16_t msg_length; - uint8_t msg_data[0]; -} __attribute__((__packed__)); +#define QMI_WMS_MESSAGE_MODE_CDMA 0x00 +#define QMI_WMS_MESSAGE_MODE_GSMWCDMA 0x01 /* Get routes for message memory storage */ #define QMI_WMS_RESULT_ROUTE_LIST 0x01 @@ -89,14 +137,6 @@ struct qmi_wms_route_list { } __attribute__((__packed__)); #define QMI_WMS_RESULT_STATUS_REPORT 0x10 /* bool */ #define QMI_WMS_PARAM_STATUS_REPORT 0x10 /* bool */ -#define QMI_WMS_RESULT_MESSAGE 0x11 -struct qmi_wms_result_message { - uint8_t ack_required; /* bool */ - uint32_t transaction_id; - uint8_t msg_format; - uint16_t msg_length; - uint8_t msg_data[0]; -} __attribute__((__packed__)); #define QMI_WMS_MSG_TYPE_P2P 0x00 #define QMI_WMS_MSG_TYPE_BROADCAST 0x01 @@ -134,3 +174,6 @@ struct qmi_wms_result_smsc_addr { #define QMI_WMS_DOMAIN_PS_PREFERRED 0x01 #define QMI_WMS_DOMAIN_CS_ONLY 0x02 #define QMI_WMS_DOMAIN_PS_ONLY 0x03 + +/* Error code */ +#define QMI_ERR_OP_DEVICE_UNSUPPORTED 0x19 diff --git a/ofono/drivers/stemodem/caif_rtnl.c b/ofono/drivers/stemodem/caif_rtnl.c index 1a42c144c..584c5a4a7 100644 --- a/ofono/drivers/stemodem/caif_rtnl.c +++ b/ofono/drivers/stemodem/caif_rtnl.c @@ -85,7 +85,7 @@ static void parse_newlink_param(struct ifinfomsg *msg, int size, if (attr->rta_type == IFLA_IFNAME && ifname != NULL) { - strncpy(ifname, RTA_DATA(attr), IF_NAMESIZE); + strncpy(ifname, RTA_DATA(attr), IF_NAMESIZE - 1); ifname[IF_NAMESIZE-1] = '\0'; break; } diff --git a/ofono/drivers/stemodem/gprs-context.c b/ofono/drivers/stemodem/gprs-context.c index 56b34388e..fb279c5a6 100644 --- a/ofono/drivers/stemodem/gprs-context.c +++ b/ofono/drivers/stemodem/gprs-context.c @@ -277,7 +277,6 @@ static void ste_gprs_activate_primary(struct ofono_gprs_context *gc, struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); struct cb_data *cbd = cb_data_new(cb, data); char buf[AUTH_BUF_LENGTH]; - int len; /* IPv6 support not implemented */ if (ctx->proto != OFONO_GPRS_PROTO_IP) @@ -291,11 +290,8 @@ static void ste_gprs_activate_primary(struct ofono_gprs_context *gc, goto error; } - len = snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\"", ctx->cid); - - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len, ",\"%s\"", - ctx->apn); + snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\",\"%s\"", + ctx->cid, ctx->apn); if (g_at_chat_send(gcd->chat, buf, none_prefix, ste_cgdcont_cb, cbd, g_free) == 0) diff --git a/ofono/drivers/swmodem/gprs-context.c b/ofono/drivers/swmodem/gprs-context.c index 5ac9a9754..002053cf9 100644 --- a/ofono/drivers/swmodem/gprs-context.c +++ b/ofono/drivers/swmodem/gprs-context.c @@ -177,9 +177,7 @@ static void sw_gprs_activate_primary(struct ofono_gprs_context *gc, break; } - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len - 3, - ",\"%s\"", ctx->apn); + snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", ctx->apn); if (g_at_chat_send(gcd->chat, buf, none_prefix, at_cgdcont_cb, cbd, g_free) > 0) diff --git a/ofono/drivers/telitmodem/gprs-context-ncm.c b/ofono/drivers/telitmodem/gprs-context-ncm.c index 244a3dc25..e2eece77f 100644 --- a/ofono/drivers/telitmodem/gprs-context-ncm.c +++ b/ofono/drivers/telitmodem/gprs-context-ncm.c @@ -346,9 +346,7 @@ static void telitncm_gprs_activate_primary(struct ofono_gprs_context *gc, break; } - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len - 3, - ",\"%s\"", ctx->apn); + snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", ctx->apn); if (g_at_chat_send(gcd->chat, buf, none_prefix, setup_cb, gc, NULL) > 0) diff --git a/ofono/drivers/ubloxmodem/gprs-context.c b/ofono/drivers/ubloxmodem/gprs-context.c index 9c319804f..b46c746e6 100644 --- a/ofono/drivers/ubloxmodem/gprs-context.c +++ b/ofono/drivers/ubloxmodem/gprs-context.c @@ -40,15 +40,26 @@ #include "ubloxmodem.h" +#define UBLOX_FLAG_DEACTIVATING 0x01 + static const char *none_prefix[] = { NULL }; static const char *cgcontrdp_prefix[] = { "+CGCONTRDP:", NULL }; static const char *uipaddr_prefix[] = { "+UIPADDR:", NULL }; +static const char *ubmconf_prefix[] = { "+UBMCONF:", NULL }; + +enum netmode { + NETWORKING_MODE_ROUTER, + NETWORKING_MODE_BRIDGE, +}; struct gprs_context_data { + const struct ublox_model *model; GAtChat *chat; unsigned int active_context; ofono_gprs_context_cb_t cb; void *cb_data; + enum netmode networking_mode; + int flags; }; static void uipaddr_cb(gboolean ok, GAtResult *result, gpointer user_data) @@ -227,6 +238,14 @@ static void ublox_read_settings(struct ofono_gprs_context *gc) { struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + if (gcd->networking_mode == NETWORKING_MODE_ROUTER) { + /* Use DHCP */ + set_gprs_context_interface(gc); + ofono_gprs_context_set_ipv4_address(gc, NULL, 0); + CALLBACK_WITH_SUCCESS(gcd->cb, gcd->cb_data); + return; + } + if (ublox_send_cgcontrdp(gc) < 0) CALLBACK_WITH_FAILURE(gcd->cb, gcd->cb_data); } @@ -305,7 +324,7 @@ static void ublox_send_uauthreq(struct ofono_gprs_context *gc, { struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); char buf[UBLOX_MAX_USER_LEN + UBLOX_MAX_PASS_LEN + 32]; - unsigned auth; + unsigned auth = 0; switch (auth_method) { case OFONO_GPRS_AUTH_METHOD_PAP: @@ -372,6 +391,14 @@ static void ublox_gprs_activate_primary(struct ofono_gprs_context *gc, { struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + if (ublox_is_toby_l4(gcd->model)) { + /* TOBY L4 does not support IPv6 */ + if (ctx->proto != OFONO_GPRS_PROTO_IP) { + CALLBACK_WITH_FAILURE(cb, data); + return; + } + } + /* IPv6 support not implemented */ if (ctx->proto != OFONO_GPRS_PROTO_IP) { CALLBACK_WITH_FAILURE(cb, data); @@ -402,6 +429,8 @@ static void cgact_disable_cb(gboolean ok, GAtResult *result, gpointer user_data) DBG("ok %d", ok); + gcd->flags &= ~UBLOX_FLAG_DEACTIVATING; + if (!ok) { CALLBACK_WITH_FAILURE(gcd->cb, gcd->cb_data); return; @@ -424,6 +453,8 @@ static void ublox_gprs_deactivate_primary(struct ofono_gprs_context *gc, gcd->cb = cb; gcd->cb_data = data; + gcd->flags |= UBLOX_FLAG_DEACTIVATING; + snprintf(buf, sizeof(buf), "AT+CGACT=0,%u", gcd->active_context); g_at_chat_send(gcd->chat, buf, none_prefix, cgact_disable_cb, gc, NULL); @@ -449,10 +480,16 @@ static void cgev_notify(GAtResult *result, gpointer user_data) sscanf(event, "%*s %*s %*s %u", &cid); else if (g_str_has_prefix(event, "NW DEACT")) sscanf(event, "%*s %*s %u", &cid); + else if (!(gcd->flags & UBLOX_FLAG_DEACTIVATING) && + g_str_has_prefix(event, "ME PDN DEACT")) + /* The modem might consider the ME deactivating without + * an explicit CGACT=0 beeing sent + */ + sscanf(event, "%*s %*s %*s %u", &cid); else return; - DBG("cid %d", cid); + DBG("cid %d, active cid: %d", cid, gcd->active_context); if ((unsigned int) cid != gcd->active_context) return; @@ -461,8 +498,44 @@ static void cgev_notify(GAtResult *result, gpointer user_data) gcd->active_context = 0; } +static void at_ubmconf_read_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_gprs_context *gc = user_data; + struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + GAtResultIter iter; + int mode; + + if (!ok) + goto error; + + g_at_result_iter_init(&iter, result); + if (!g_at_result_iter_next(&iter, "+UBMCONF:")) + goto error; + + if (!g_at_result_iter_next_number(&iter, &mode)) + goto error; + + switch (mode) { + case 1: + gcd->networking_mode = NETWORKING_MODE_ROUTER; + break; + case 2: + gcd->networking_mode = NETWORKING_MODE_BRIDGE; + break; + default: + goto error; + } + + return; + +error: + ofono_error("AT+UBMCONF? failed; assuming router mode"); + gcd->networking_mode = NETWORKING_MODE_ROUTER; +} + static int ublox_gprs_context_probe(struct ofono_gprs_context *gc, - unsigned int vendor, void *data) + unsigned int model_id, void *data) { GAtChat *chat = data; struct gprs_context_data *gcd; @@ -473,10 +546,23 @@ static int ublox_gprs_context_probe(struct ofono_gprs_context *gc, if (gcd == NULL) return -ENOMEM; + gcd->model = ublox_model_from_id(model_id); + if (!gcd->model) + return -EINVAL; + gcd->chat = g_at_chat_clone(chat); ofono_gprs_context_set_data(gc, gcd); + if (ublox_is_toby_l2(gcd->model)) { + g_at_chat_send(chat, "AT+UBMCONF?", ubmconf_prefix, + at_ubmconf_read_cb, gc, NULL); + } else if (ublox_is_toby_l4(gcd->model)) { + gcd->networking_mode = NETWORKING_MODE_ROUTER; + } else { + gcd->networking_mode = NETWORKING_MODE_ROUTER; + } + g_at_chat_register(chat, "+CGEV:", cgev_notify, FALSE, gc, NULL); return 0; diff --git a/ofono/drivers/ubloxmodem/lte.c b/ofono/drivers/ubloxmodem/lte.c index 34397dfe1..eb9062d98 100644 --- a/ofono/drivers/ubloxmodem/lte.c +++ b/ofono/drivers/ubloxmodem/lte.c @@ -39,24 +39,85 @@ #include "ubloxmodem.h" -static const char *ucgdflt_prefix[] = { "+UCGDFLT:", NULL }; +static const char *none_prefix[] = { NULL }; struct lte_driver_data { GAtChat *chat; + const struct ublox_model *model; + struct ofono_lte_default_attach_info pending_info; }; -static void ucgdflt_cb(gboolean ok, GAtResult *result, gpointer user_data) +static void at_lte_set_auth_cb(gboolean ok, GAtResult *result, + gpointer user_data) { struct cb_data *cbd = user_data; ofono_lte_cb_t cb = cbd->cb; struct ofono_error error; - DBG("ok %d", ok); - decode_at_error(&error, g_at_result_final_response(result)); cb(&error, cbd->data); } +static void at_lte_set_default_attach_info_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_lte_cb_t cb = cbd->cb; + void *data = cbd->data; + struct lte_driver_data *ldd = cbd->user; + struct ofono_error error; + char buf[32 + OFONO_GPRS_MAX_USERNAME_LENGTH + + OFONO_GPRS_MAX_PASSWORD_LENGTH + 1]; + enum ofono_gprs_auth_method auth_method; + int cid; + + if (!ok) { + decode_at_error(&error, g_at_result_final_response(result)); + cb(&error, data); + return; + } + + if (ublox_is_toby_l2(ldd->model)) { + /* If CGDCONT has already been used to set up cid 4 then + * the EPS default bearer will be configured from another + * cid (see documentation for how this is selected). Avoid + * doing so as this assumes as much... + */ + cid = 4; + } else if (ublox_is_toby_l4(ldd->model)) { + cid = 1; + } else { + ofono_error("Unknown model; " + "unable to determine EPS default bearer CID"); + goto out; + } + + auth_method = ldd->pending_info.auth_method; + + /* change the authentication method if the parameters are invalid */ + if (!*ldd->pending_info.username || !*ldd->pending_info.password) + auth_method = OFONO_GPRS_AUTH_METHOD_NONE; + + /* In contrast to CGAUTH, all four parameters are _required_ here; + * if auth type is NONE then username and password must be set to + * empty strings. + */ + sprintf(buf, "AT+UAUTHREQ=%d,%d,\"%s\",\"%s\"", + cid, + at_util_gprs_auth_method_to_auth_prot(auth_method), + ldd->pending_info.username, + ldd->pending_info.password); + + cbd = cb_data_ref(cbd); + if (g_at_chat_send(ldd->chat, buf, none_prefix, + at_lte_set_auth_cb, cbd, cb_data_unref) > 0) + return; + +out: + cb_data_unref(cbd); + CALLBACK_WITH_FAILURE(cb, data); +} + static void ublox_lte_set_default_attach_info(const struct ofono_lte *lte, const struct ofono_lte_default_attach_info *info, ofono_lte_cb_t cb, void *data) @@ -67,17 +128,32 @@ static void ublox_lte_set_default_attach_info(const struct ofono_lte *lte, DBG("LTE config with APN: %s", info->apn); - if (strlen(info->apn) > 0) - snprintf(buf, sizeof(buf), "AT+UCGDFLT=0,\"IP\",\"%s\"", + cbd->user = ldd; + memcpy(&ldd->pending_info, info, sizeof(ldd->pending_info)); + + if (ublox_is_toby_l2(ldd->model)) { + if (strlen(info->apn) > 0) + snprintf(buf, sizeof(buf), "AT+UCGDFLT=0,%s,\"%s\"", + at_util_gprs_proto_to_pdp_type(info->proto), + info->apn); + else + snprintf(buf, sizeof(buf), "AT+UCGDFLT=0"); + + } else if (ublox_is_toby_l4(ldd->model)) { + if (strlen(info->apn) > 0) + snprintf(buf, sizeof(buf), "AT+CGDCONT=1,%s,\"%s\"", + at_util_gprs_proto_to_pdp_type(info->proto), info->apn); - else - snprintf(buf, sizeof(buf), "AT+UCGDFLT=0"); + else + snprintf(buf, sizeof(buf), "AT+CGDCONT=1"); + } - /* We can't do much in case of failure so don't check response. */ - if (g_at_chat_send(ldd->chat, buf, ucgdflt_prefix, - ucgdflt_cb, cbd, g_free) > 0) + if (g_at_chat_send(ldd->chat, buf, none_prefix, + at_lte_set_default_attach_info_cb, + cbd, cb_data_unref) > 0) return; + cb_data_unref(cbd); CALLBACK_WITH_FAILURE(cb, data); } @@ -91,7 +167,7 @@ static gboolean lte_delayed_register(gpointer user_data) } static int ublox_lte_probe(struct ofono_lte *lte, - unsigned int vendor, void *data) + unsigned int model_id, void *data) { GAtChat *chat = data; struct lte_driver_data *ldd; @@ -103,6 +179,7 @@ static int ublox_lte_probe(struct ofono_lte *lte, return -ENOMEM; ldd->chat = g_at_chat_clone(chat); + ldd->model = ublox_model_from_id(model_id); ofono_lte_set_data(lte, ldd); diff --git a/ofono/drivers/ubloxmodem/network-registration.c b/ofono/drivers/ubloxmodem/network-registration.c new file mode 100644 index 000000000..25f239a60 --- /dev/null +++ b/ofono/drivers/ubloxmodem/network-registration.c @@ -0,0 +1,552 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2010 ST-Ericsson AB. + * Copyright (C) 2019 Norrbonn AB + * + * 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 "gatchat.h" +#include "gatresult.h" + +#include "common.h" +#include "ubloxmodem.h" +#include "drivers/atmodem/vendor.h" + +#include "drivers/atmodem/network-registration.h" + +static const char *none_prefix[] = { NULL }; +static const char *cmer_prefix[] = { "+CMER:", NULL }; +static const char *ureg_prefix[] = { "+UREG:", NULL }; +static const char *creg_prefix[] = { "+CREG:", NULL }; + +struct netreg_data { + struct at_netreg_data at_data; + + const struct ublox_model *model; + bool updating_status : 1; +}; + +struct tech_query { + int status; + int lac; + int ci; + int tech; + struct ofono_netreg *netreg; +}; + +static void ciev_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); + int strength, ind; + GAtResultIter iter; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CIEV:")) + return; + + if (!g_at_result_iter_next_number(&iter, &ind)) + return; + + if (ind != nd->signal_index) + return; + + if (!g_at_result_iter_next_number(&iter, &strength)) + return; + + if (strength == nd->signal_invalid) + strength = -1; + else + strength = (strength * 100) / (nd->signal_max - nd->signal_min); + + ofono_netreg_strength_notify(netreg, strength); +} + +static gboolean notify_time(gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); + + nd->nitz_timeout = 0; + + ofono_netreg_time_notify(netreg, &nd->time); + + return FALSE; +} + +static void ctzdst_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); + int dst; + GAtResultIter iter; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CTZDST:")) + return; + + if (!g_at_result_iter_next_number(&iter, &dst)) + return; + + DBG("dst %d", dst); + + nd->time.dst = dst; + + if (nd->nitz_timeout > 0) { + g_source_remove(nd->nitz_timeout); + nd->nitz_timeout = 0; + } + + ofono_netreg_time_notify(netreg, &nd->time); +} + +static void ctzv_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); + int year, mon, mday, hour, min, sec; + const char *tz, *time; + GAtResultIter iter; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CTZV:")) + return; + + if (!g_at_result_iter_next_unquoted_string(&iter, &tz)) + return; + + if (!g_at_result_iter_next_string(&iter, &time)) + return; + + DBG("tz %s time %s", tz, time); + + if (sscanf(time, "%u/%u/%u,%u:%u:%u", &year, &mon, &mday, + &hour, &min, &sec) != 6) + return; + + nd->time.sec = sec; + nd->time.min = min; + nd->time.hour = hour; + nd->time.mday = mday; + nd->time.mon = mon; + nd->time.year = 2000 + year; + + nd->time.utcoff = atoi(tz) * 15 * 60; + + /* Delay notification in case there's a DST update coming */ + if (nd->nitz_timeout > 0) + g_source_remove(nd->nitz_timeout); + + nd->nitz_timeout = g_timeout_add_seconds(1, notify_time, user_data); +} + +static void ctze_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct at_netreg_data *nd = ofono_netreg_get_data(netreg); + int year, mon, mday, hour, min, sec; + int dst; + const char *tz, *time; + GAtResultIter iter; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CTZE:")) + return; + + if (!g_at_result_iter_next_unquoted_string(&iter, &tz)) + return; + + if (!g_at_result_iter_next_number(&iter, &dst)) + return; + + if (!g_at_result_iter_next_string(&iter, &time)) + return; + + DBG("tz %s dst %d time %s", tz, dst, time); + + if (sscanf(time, "%u/%u/%u,%u:%u:%u", &year, &mon, &mday, + &hour, &min, &sec) != 6) + return; + + nd->time.sec = sec; + nd->time.min = min; + nd->time.hour = hour; + nd->time.mday = mday; + nd->time.mon = mon; + nd->time.year = 2000 + year; + + nd->time.utcoff = atoi(tz) * 15 * 60; + nd->time.dst = dst; + + ofono_netreg_time_notify(netreg, &nd->time); +} + +static int ublox_ureg_state_to_tech(int state) +{ + switch (state) { + case 1: + return ACCESS_TECHNOLOGY_GSM; + case 2: + return ACCESS_TECHNOLOGY_GSM_EGPRS; + case 3: + return ACCESS_TECHNOLOGY_UTRAN; + case 4: + return ACCESS_TECHNOLOGY_UTRAN_HSDPA; + case 5: + return ACCESS_TECHNOLOGY_UTRAN_HSUPA; + case 6: + return ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA; + case 7: + return ACCESS_TECHNOLOGY_EUTRAN; + case 8: + return ACCESS_TECHNOLOGY_GSM; + case 9: + return ACCESS_TECHNOLOGY_GSM_EGPRS; + default: + /* Not registered for PS (0) or something unknown (>9)... */ + return -1; + } +} + +static gboolean is_registered(int status) +{ + return status == NETWORK_REGISTRATION_STATUS_REGISTERED || + status == NETWORK_REGISTRATION_STATUS_ROAMING; +} + +static void ublox_creg_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct tech_query *tq = user_data; + struct netreg_data *nd = ofono_netreg_get_data(tq->netreg); + int status; + int lac; + int ci; + int tech; + + nd->updating_status = false; + + if (!ok) + return; + + if (at_util_parse_reg(result, "+CREG:", NULL, &status, + &lac, &ci, &tech, OFONO_VENDOR_GENERIC) == FALSE) + return; + + /* The query provided a tech, use that */ + if (is_registered(status) && tq->tech != -1) + tech = tq->tech; + + ofono_netreg_status_notify(tq->netreg, status, lac, ci, tech); +} + +static void ublox_ureg_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct tech_query *tq = user_data; + struct netreg_data *nd = ofono_netreg_get_data(tq->netreg); + GAtResultIter iter; + gint enabled, state; + int tech = tq->tech; + + nd->updating_status = false; + + if (!ok) + goto error; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+UREG:")) + return; + + if (!g_at_result_iter_next_number(&iter, &enabled)) + return; + + if (!g_at_result_iter_next_number(&iter, &state)) + return; + + tech = ublox_ureg_state_to_tech(state); + if (tech < 0) + /* No valid UREG status, we have to trust CREG... */ + tech = tq->tech; + +error: + ofono_netreg_status_notify(tq->netreg, + tq->status, tq->lac, tq->ci, tech); +} + +static void ureg_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct tech_query *tq; + GAtResultIter iter; + int state; + + if (nd->updating_status) + return; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+UREG:")) + return; + + if (!g_at_result_iter_next_number(&iter, &state)) + return; + + tq = g_new0(struct tech_query, 1); + + tq->tech = ublox_ureg_state_to_tech(state); + tq->netreg = netreg; + + if (g_at_chat_send(nd->at_data.chat, "AT+CREG?", creg_prefix, + ublox_creg_cb, tq, g_free) > 0) { + nd->updating_status = true; + return; + } + + g_free(tq); +} + +static void creg_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct tech_query *tq; + int status; + int lac; + int ci; + int tech; + + if (nd->updating_status) + return; + + if (at_util_parse_reg_unsolicited(result, "+CREG:", &status, + &lac, &ci, &tech, OFONO_VENDOR_GENERIC) == FALSE) + return; + + if (!is_registered(status)) + goto notify; + + if (ublox_is_toby_l4(nd->model) || ublox_is_toby_l2(nd->model)) { + tq = g_new0(struct tech_query, 1); + + tq->status = status; + tq->lac = lac; + tq->ci = ci; + tq->tech = tech; + tq->netreg = netreg; + + if (g_at_chat_send(nd->at_data.chat, "AT+UREG?", ureg_prefix, + ublox_ureg_cb, tq, g_free) > 0) { + nd->updating_status = true; + return; + } + + g_free(tq); + } + + if (tech == -1) + tech = nd->at_data.tech; + +notify: + ofono_netreg_status_notify(netreg, status, lac, ci, tech); +} + +static void at_cmer_not_supported(struct ofono_netreg *netreg) +{ + ofono_error("+CMER not supported by this modem. If this is an error" + " please submit patches to support this hardware"); + + ofono_netreg_remove(netreg); +} + +static void ublox_finish_registration(struct ofono_netreg *netreg) +{ + struct netreg_data *nd = ofono_netreg_get_data(netreg); + + if (ublox_is_toby_l4(nd->model) || ublox_is_toby_l2(nd->model)) + g_at_chat_register(nd->at_data.chat, "+UREG:", + ureg_notify, FALSE, netreg, NULL); + + g_at_chat_register(nd->at_data.chat, "+CIEV:", + ciev_notify, FALSE, netreg, NULL); + + g_at_chat_register(nd->at_data.chat, "+CREG:", + creg_notify, FALSE, netreg, NULL); + + ofono_netreg_register(netreg); +} + +static void ublox_ureg_set_cb(gboolean ok, + GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + + if (!ok) { + ofono_error("Unable to initialize Network Registration"); + ofono_netreg_remove(netreg); + return; + } + + ublox_finish_registration(netreg); +} + +static void ublox_cmer_set_cb(gboolean ok, + GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct netreg_data *nd = ofono_netreg_get_data(netreg); + + if (!ok) { + at_cmer_not_supported(netreg); + return; + } + + if (ublox_is_toby_l4(nd->model) || ublox_is_toby_l2(nd->model)) { + g_at_chat_send(nd->at_data.chat, "AT+UREG=1", none_prefix, + ublox_ureg_set_cb, netreg, NULL); + + return; + } + + ublox_finish_registration(netreg); +} + +static void ublox_creg_set_cb(gboolean ok, + GAtResult *result, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct netreg_data *nd = ofono_netreg_get_data(netreg); + + if (!ok) { + ofono_error("Unable to initialize Network Registration"); + ofono_netreg_remove(netreg); + return; + } + + if (ublox_is_toby_l4(nd->model)) + /* FIXME */ + ofono_error("TOBY L4 requires polling of ECSQ"); + + /* Register for network time update reports */ + if (ublox_is_toby_l2(nd->model)) { + /* TOBY L2 does not support CTZDST */ + g_at_chat_register(nd->at_data.chat, "+CTZE:", ctze_notify, + FALSE, netreg, NULL); + g_at_chat_send(nd->at_data.chat, "AT+CTZR=2", none_prefix, + NULL, NULL, NULL); + } else { + g_at_chat_register(nd->at_data.chat, "+CTZV:", ctzv_notify, + FALSE, netreg, NULL); + g_at_chat_register(nd->at_data.chat, "+CTZDST:", ctzdst_notify, + FALSE, netreg, NULL); + g_at_chat_send(nd->at_data.chat, "AT+CTZR=1", none_prefix, + NULL, NULL, NULL); + } + + /* AT+CMER NOTES: + * - For all u-blox models, mode 3 is equivalent to mode 1; + * since some models do not support setting modes 2 nor 3 + * (see UBX-13002752), we prefer mode 1 for all models. + * - The TOBY L4 does not support ind=2 + */ + g_at_chat_send(nd->at_data.chat, "AT+CMER=1,0,0,1", cmer_prefix, + ublox_cmer_set_cb, netreg, NULL); +} + +/* + * uBlox netreg atom probe. + * - takes uBlox model ID parameter instead of AT vendor ID + */ +static int ublox_netreg_probe(struct ofono_netreg *netreg, + unsigned int model_id, + void *data) +{ + GAtChat *chat = data; + struct netreg_data *nd; + + nd = g_new0(struct netreg_data, 1); + + nd->model = ublox_model_from_id(model_id); + + /* There should be no uBlox-specific quirks in the 'generic' + * AT driver + */ + nd->at_data.vendor = OFONO_VENDOR_GENERIC; + + nd->at_data.chat = g_at_chat_clone(chat); + nd->at_data.tech = -1; + nd->at_data.time.sec = -1; + nd->at_data.time.min = -1; + nd->at_data.time.hour = -1; + nd->at_data.time.mday = -1; + nd->at_data.time.mon = -1; + nd->at_data.time.year = -1; + nd->at_data.time.dst = 0; + nd->at_data.time.utcoff = 0; + ofono_netreg_set_data(netreg, nd); + + /* All uBlox devices support n=2 so no need to query this */ + g_at_chat_send(nd->at_data.chat, "AT+CREG=2", none_prefix, + ublox_creg_set_cb, netreg, NULL); + + return 0; +} + +static const struct ofono_netreg_driver driver = { + .name = "ubloxmodem", + .probe = ublox_netreg_probe, + .remove = at_netreg_remove, + .registration_status = at_registration_status, + .current_operator = at_current_operator, + .list_operators = at_list_operators, + .register_auto = at_register_auto, + .register_manual = at_register_manual, + .strength = at_signal_strength, +}; + +void ublox_netreg_init(void) +{ + ofono_netreg_driver_register(&driver); +} + +void ublox_netreg_exit(void) +{ + ofono_netreg_driver_unregister(&driver); +} diff --git a/ofono/drivers/ubloxmodem/ubloxmodem.c b/ofono/drivers/ubloxmodem/ubloxmodem.c index a325b1f08..034f7db1a 100644 --- a/ofono/drivers/ubloxmodem/ubloxmodem.c +++ b/ofono/drivers/ubloxmodem/ubloxmodem.c @@ -23,6 +23,8 @@ #include #endif +#include + #include #include @@ -33,9 +35,96 @@ #include "ubloxmodem.h" +const struct ublox_model ublox_models[] = { + { + .name = "SARA-G270", + }, + /* TOBY L2 series */ + { + .name = "TOBY-L200", + .flags = UBLOX_F_TOBY_L2|UBLOX_F_HAVE_USBCONF, + }, + { + .name = "TOBY-L201", + .flags = UBLOX_F_TOBY_L2|UBLOX_F_HAVE_USBCONF, + }, + { + .name = "TOBY-L210", + .flags = UBLOX_F_TOBY_L2|UBLOX_F_HAVE_USBCONF, + }, + { + .name = "TOBY-L220", + .flags = UBLOX_F_TOBY_L2|UBLOX_F_HAVE_USBCONF, + }, + { + .name = "TOBY-L280", + .flags = UBLOX_F_TOBY_L2|UBLOX_F_HAVE_USBCONF, + }, + /* TOBY L4 series */ + { + .name = "TOBY-L4006", + .flags = UBLOX_F_TOBY_L4, + }, + { + .name = "TOBY-L4106", + .flags = UBLOX_F_TOBY_L4, + }, + { + .name = "TOBY-L4206", + .flags = UBLOX_F_TOBY_L4, + }, + { + .name = "TOBY-L4906", + .flags = UBLOX_F_TOBY_L4, + }, + /* LARA L2 series */ + { + .name = "LARA-R202", + .flags = UBLOX_F_LARA_R2, + }, + { + .name = "LARA-R211", + .flags = UBLOX_F_LARA_R2, + }, + { /* sentinel */ }, +}; + +const struct ublox_model *ublox_model_from_name(const char *name) +{ + const struct ublox_model *m; + + for (m = ublox_models; m->name; m++) { + if (!strcmp(name, m->name)) + return m; + } + + return NULL; +} + +const struct ublox_model *ublox_model_from_id(int id) +{ + return ublox_models + id; +} + +int ublox_model_to_id(const struct ublox_model *model) +{ + return model - ublox_models; +} + +int ublox_is_toby_l2(const struct ublox_model *model) +{ + return model->flags & UBLOX_F_TOBY_L2; +} + +int ublox_is_toby_l4(const struct ublox_model *model) +{ + return model->flags & UBLOX_F_TOBY_L4; +} + static int ubloxmodem_init(void) { ublox_gprs_context_init(); + ublox_netreg_init(); ublox_netmon_init(); ublox_lte_init(); @@ -45,6 +134,7 @@ static int ubloxmodem_init(void) static void ubloxmodem_exit(void) { ublox_gprs_context_exit(); + ublox_netreg_exit(); ublox_netmon_exit(); ublox_lte_exit(); } diff --git a/ofono/drivers/ubloxmodem/ubloxmodem.h b/ofono/drivers/ubloxmodem/ubloxmodem.h index bfb010647..0bd7ef239 100644 --- a/ofono/drivers/ubloxmodem/ubloxmodem.h +++ b/ofono/drivers/ubloxmodem/ubloxmodem.h @@ -23,9 +23,30 @@ #define UBLOXMODEM "ubloxmodem" +enum ublox_flags { + UBLOX_F_TOBY_L2 = (1 << 0), + UBLOX_F_TOBY_L4 = (1 << 1), + UBLOX_F_LARA_R2 = (1 << 2), + UBLOX_F_HAVE_USBCONF = (1 << 3), +}; + +struct ublox_model { + char *name; + int flags; +}; + +const struct ublox_model *ublox_model_from_name(const char *name); +const struct ublox_model *ublox_model_from_id(int id); +int ublox_model_to_id(const struct ublox_model *model); +int ublox_is_toby_l2(const struct ublox_model *model); +int ublox_is_toby_l4(const struct ublox_model *model); + extern void ublox_gprs_context_init(void); extern void ublox_gprs_context_exit(void); +void ublox_netreg_init(void); +void ublox_netreg_exit(void); + extern void ublox_netmon_init(void); extern void ublox_netmon_exit(void); diff --git a/ofono/drivers/xmm7modem/netmon.c b/ofono/drivers/xmm7modem/netmon.c index ba70e2bd6..0e958abef 100644 --- a/ofono/drivers/xmm7modem/netmon.c +++ b/ofono/drivers/xmm7modem/netmon.c @@ -47,6 +47,7 @@ static const char *xmci_prefix[] = { "+XMCI:", NULL }; struct netmon_driver_data { GAtChat *chat; + int xmci_mode; }; enum xmci_ofono_type_info { @@ -85,6 +86,7 @@ static void xmci_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; struct ofono_netmon *netmon = cbd->data; + struct netmon_driver_data *nmd = ofono_netmon_get_data(netmon); ofono_netmon_cb_t cb = cbd->cb; struct ofono_error error; GAtResultIter iter; @@ -96,6 +98,11 @@ static void xmci_cb(gboolean ok, GAtResult *result, gpointer user_data) int ecn0 = -1; int rsrq = -1; int tech = -1; + int type = -1; + int ci = -1; + const char *cell_id; + char mcc[3]; + char mnc[3]; DBG("ok %d", ok); @@ -109,18 +116,23 @@ static void xmci_cb(gboolean ok, GAtResult *result, gpointer user_data) g_at_result_iter_init(&iter, result); while (g_at_result_iter_next(&iter, "+XMCI:")) { - if (!g_at_result_iter_next_number(&iter, &number)) + if (!g_at_result_iter_next_number(&iter, &type)) break; - tech = xmm7modem_map_radio_access_technology(number); + tech = xmm7modem_map_radio_access_technology(type); - switch (number) { + switch (type) { + case XMCI_GSM_NEIGH_CELL: case XMCI_GSM_SERV_CELL: - /* skip ,,,, */ - g_at_result_iter_skip_next(&iter); - g_at_result_iter_skip_next(&iter); - g_at_result_iter_skip_next(&iter); + /* ,,,, */ + g_at_result_iter_next_number(&iter, &number); + snprintf(mcc, 3, "%d", number); + g_at_result_iter_next_number(&iter, &number); + snprintf(mnc, 3, "%d", number); g_at_result_iter_skip_next(&iter); + g_at_result_iter_next_string(&iter, &cell_id); + sscanf(&cell_id[2], "%x", &number); + ci = number != -1 ? number : 0; g_at_result_iter_skip_next(&iter); g_at_result_iter_next_number(&iter, &number); @@ -129,15 +141,20 @@ static void xmci_cb(gboolean ok, GAtResult *result, gpointer user_data) g_at_result_iter_next_number(&iter, &number); ber = number != 99 ? number : ber; break; + case XMCI_UMTS_NEIGH_CELL: case XMCI_UMTS_SERV_CELL: /* - * skip ,,,,, + * ,,,,, * ,, */ + g_at_result_iter_next_number(&iter, &number); + snprintf(mcc, 3, "%d", number); + g_at_result_iter_next_number(&iter, &number); + snprintf(mnc, 3, "%d", number); g_at_result_iter_skip_next(&iter); - g_at_result_iter_skip_next(&iter); - g_at_result_iter_skip_next(&iter); - g_at_result_iter_skip_next(&iter); + g_at_result_iter_next_string(&iter, &cell_id); + sscanf(&cell_id[2], "%x", &number); + ci = number != -1 ? number : 0; g_at_result_iter_skip_next(&iter); g_at_result_iter_skip_next(&iter); g_at_result_iter_skip_next(&iter); @@ -150,15 +167,20 @@ static void xmci_cb(gboolean ok, GAtResult *result, gpointer user_data) g_at_result_iter_next_number(&iter, &number); ecn0 = number != 255 ? number : ecn0; break; + case XMCI_LTE_NEIGH_CELL: case XMCI_LTE_SERV_CELL: /* - * skip ,,,,,, + * ,,,,,, * , */ + g_at_result_iter_next_number(&iter, &number); + snprintf(mcc, 3, "%d", number); + g_at_result_iter_next_number(&iter, &number); + snprintf(mnc, 3, "%d", number); g_at_result_iter_skip_next(&iter); - g_at_result_iter_skip_next(&iter); - g_at_result_iter_skip_next(&iter); - g_at_result_iter_skip_next(&iter); + g_at_result_iter_next_string(&iter, &cell_id); + sscanf(&cell_id[2], "%x", &number); + ci = number != -1 ? number : 0; g_at_result_iter_skip_next(&iter); g_at_result_iter_skip_next(&iter); g_at_result_iter_skip_next(&iter); @@ -174,8 +196,15 @@ static void xmci_cb(gboolean ok, GAtResult *result, gpointer user_data) break; } - ofono_netmon_serving_cell_notify(netmon, + if ((nmd->xmci_mode == 0) && + (type == XMCI_GSM_NEIGH_CELL || + type == XMCI_UMTS_NEIGH_CELL || + type == XMCI_LTE_NEIGH_CELL)) { + ofono_netmon_neighbouring_cell_notify(netmon, tech, + OFONO_NETMON_INFO_MCC, mcc, + OFONO_NETMON_INFO_MNC, mnc, + OFONO_NETMON_INFO_CI, ci, OFONO_NETMON_INFO_RXLEV, rxlev, OFONO_NETMON_INFO_BER, ber, OFONO_NETMON_INFO_RSCP, rscp, @@ -183,10 +212,25 @@ static void xmci_cb(gboolean ok, GAtResult *result, gpointer user_data) OFONO_NETMON_INFO_RSRQ, rsrq, OFONO_NETMON_INFO_RSRP, rsrp, OFONO_NETMON_INFO_INVALID); - - CALLBACK_WITH_SUCCESS(cb, cbd->data); - break; + } else if ((nmd->xmci_mode == 1) && + (type == XMCI_GSM_SERV_CELL || + type == XMCI_UMTS_SERV_CELL || + type == XMCI_LTE_SERV_CELL)) { + ofono_netmon_serving_cell_notify(netmon, + tech, + OFONO_NETMON_INFO_RXLEV, rxlev, + OFONO_NETMON_INFO_BER, ber, + OFONO_NETMON_INFO_RSCP, rscp, + OFONO_NETMON_INFO_ECN0, ecn0, + OFONO_NETMON_INFO_RSRQ, rsrq, + OFONO_NETMON_INFO_RSRP, rsrp, + OFONO_NETMON_INFO_INVALID); + break; + } } + + CALLBACK_WITH_SUCCESS(cb, cbd->data); + nmd->xmci_mode = -1; } static void xmm7modem_netmon_request_update(struct ofono_netmon *netmon, @@ -194,6 +238,7 @@ static void xmm7modem_netmon_request_update(struct ofono_netmon *netmon, { struct netmon_driver_data *nmd = ofono_netmon_get_data(netmon); struct cb_data *cbd = cb_data_new(cb, data); + nmd->xmci_mode = 1; DBG("xmm7modem netmon request update"); @@ -205,6 +250,23 @@ static void xmm7modem_netmon_request_update(struct ofono_netmon *netmon, CALLBACK_WITH_FAILURE(cb, data); } +static void xmm7modem_neighbouring_cell_update(struct ofono_netmon *netmon, + ofono_netmon_cb_t cb, void *data) +{ + struct netmon_driver_data *nmd = ofono_netmon_get_data(netmon); + struct cb_data *cbd = cb_data_new(cb, data); + nmd->xmci_mode = 0; + + DBG("xmm7modem netmon request neighbouring cell update"); + + if (g_at_chat_send(nmd->chat, "AT+XMCI=0", xmci_prefix, + xmci_cb, cbd, g_free) > 0) + return; + + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); +} + static gboolean ril_delayed_register(gpointer user_data) { struct ofono_netmon *netmon = user_data; @@ -224,6 +286,7 @@ static int xmm7modem_netmon_probe(struct ofono_netmon *netmon, nmd = g_new0(struct netmon_driver_data, 1); nmd->chat = g_at_chat_clone(chat); + nmd->xmci_mode = -1; ofono_netmon_set_data(netmon, nmd); @@ -250,6 +313,7 @@ static const struct ofono_netmon_driver driver = { .probe = xmm7modem_netmon_probe, .remove = xmm7modem_netmon_remove, .request_update = xmm7modem_netmon_request_update, + .neighbouring_cell_update = xmm7modem_neighbouring_cell_update, }; void xmm_netmon_init(void) diff --git a/ofono/drivers/xmm7modem/radio-settings.c b/ofono/drivers/xmm7modem/radio-settings.c index f1eb1aa9b..c7c2ce0e8 100644 --- a/ofono/drivers/xmm7modem/radio-settings.c +++ b/ofono/drivers/xmm7modem/radio-settings.c @@ -84,10 +84,10 @@ static void xact_query_cb(gboolean ok, GAtResult *result, gpointer user_data) mode = OFONO_RADIO_ACCESS_MODE_LTE; break; case 3: - mode = OFONO_RADIO_ACCESS_MODE_UMTS; + mode = OFONO_RADIO_ACCESS_MODE_UMTS|OFONO_RADIO_ACCESS_MODE_GSM; break; case 4: - mode = OFONO_RADIO_ACCESS_MODE_LTE; + mode = OFONO_RADIO_ACCESS_MODE_LTE|OFONO_RADIO_ACCESS_MODE_UMTS; break; case 5: mode = OFONO_RADIO_ACCESS_MODE_LTE; @@ -158,7 +158,16 @@ static void xmm_set_rat_mode(struct ofono_radio_settings *rs, break; } - if (value == 6) + if (mode == + (OFONO_RADIO_ACCESS_MODE_UMTS|OFONO_RADIO_ACCESS_MODE_GSM)) { + value = 3; + preferred = 1; + } + + if (mode == (OFONO_RADIO_ACCESS_MODE_LTE|OFONO_RADIO_ACCESS_MODE_UMTS)) + value = 4; + + if (value == 6 || value == 3 || value == 4) snprintf(buf, sizeof(buf), "AT+XACT=%u,%u", value, preferred); else snprintf(buf, sizeof(buf), "AT+XACT=%u", value); diff --git a/ofono/gatchat/gatchat.c b/ofono/gatchat/gatchat.c index 3f290ac20..9e7771074 100644 --- a/ofono/gatchat/gatchat.c +++ b/ofono/gatchat/gatchat.c @@ -1047,6 +1047,29 @@ static guint at_chat_send_common(struct at_chat *chat, guint gid, return c->id; } +static gboolean at_chat_retry(struct at_chat *chat, guint id) +{ + struct at_command *cmd = g_queue_peek_head(chat->command_queue); + + if (!cmd) + return FALSE; + + /* do nothing if command is not yet started, or already finished */ + if (cmd->id != id) + return FALSE; + + /* do nothing if command is not fully written */ + if (chat->cmd_bytes_written != strlen(cmd->cmd)) + return FALSE; + + /* reset number of written bytes to re-write command */ + chat->cmd_bytes_written = 0; + + chat_wakeup_writer(chat); + + return TRUE; +} + static struct at_notify *at_notify_create(struct at_chat *chat, const char *prefix, gboolean pdu) @@ -1543,6 +1566,14 @@ guint g_at_chat_send_and_expect_short_prompt(GAtChat *chat, const char *cmd, NULL, func, user_data, notify); } +gboolean g_at_chat_retry(GAtChat *chat, guint id) +{ + if (chat == NULL || id == 0) + return FALSE; + + return at_chat_retry(chat->parent, id); +} + gboolean g_at_chat_cancel(GAtChat *chat, guint id) { /* We use id 0 for wakeup commands */ diff --git a/ofono/gatchat/gatchat.h b/ofono/gatchat/gatchat.h index 7290b34fa..328703181 100644 --- a/ofono/gatchat/gatchat.h +++ b/ofono/gatchat/gatchat.h @@ -147,6 +147,13 @@ guint g_at_chat_send_and_expect_short_prompt(GAtChat *chat, const char *cmd, const char **valid_resp, GAtResultFunc func, gpointer user_data, GDestroyNotify notify); +/*! + * Retry an already created command. This does nothing if the command is + * still waiting in the queue. If the command has been written to the channel, + * but no response is received yet, the retry writes the command again. + */ +gboolean g_at_chat_retry(GAtChat *chat, guint id); + gboolean g_at_chat_cancel(GAtChat *chat, guint id); gboolean g_at_chat_cancel_all(GAtChat *chat); diff --git a/ofono/gatchat/gatmux.c b/ofono/gatchat/gatmux.c index 9660006b0..4d89f0398 100644 --- a/ofono/gatchat/gatmux.c +++ b/ofono/gatchat/gatmux.c @@ -231,6 +231,7 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond, int i; GIOStatus status; gsize bytes_read; + gboolean buffer_full = FALSE; if (cond & G_IO_NVAL) return FALSE; @@ -255,6 +256,8 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond, if (mux->buf_used > 0) memmove(mux->buf, mux->buf + nread, mux->buf_used); + g_at_mux_ref(mux); + for (i = 1; i <= MAX_CHANNELS; i++) { int offset = i / 8; int bit = i % 8; @@ -267,6 +270,10 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond, dispatch_sources(mux->dlcs[i-1], G_IO_IN); } + + buffer_full = mux->buf_used == sizeof(mux->buf); + + g_at_mux_unref(mux); } if (cond & (G_IO_HUP | G_IO_ERR)) @@ -275,7 +282,7 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond, if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN) return FALSE; - if (mux->buf_used == sizeof(mux->buf)) + if (buffer_full) return FALSE; return TRUE; @@ -646,13 +653,6 @@ void g_at_mux_unref(GAtMux *mux) } } -static void read_watcher_destroy_notify(gpointer user_data) -{ - GAtMux *mux = user_data; - - mux->read_watch = 0; -} - gboolean g_at_mux_start(GAtMux *mux) { if (mux->channel == NULL) @@ -666,8 +666,7 @@ gboolean g_at_mux_start(GAtMux *mux) mux->read_watch = g_io_add_watch_full(mux->channel, G_PRIORITY_DEFAULT, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - received_data, mux, - read_watcher_destroy_notify); + received_data, mux, NULL); mux->shutdown = FALSE; @@ -684,8 +683,10 @@ gboolean g_at_mux_shutdown(GAtMux *mux) if (mux->channel == NULL) return FALSE; - if (mux->read_watch > 0) + if (mux->read_watch > 0) { g_source_remove(mux->read_watch); + mux->read_watch = 0; + } if (mux->write_watch > 0) g_source_remove(mux->write_watch); @@ -952,8 +953,7 @@ gboolean g_at_mux_setup_gsm0710(GAtChat *chat, mux_query_cb, msd, msd_free) > 0) return TRUE; - if (msd) - msd_free(msd); + msd_free(msd); return FALSE; } diff --git a/ofono/gatchat/gatresult.c b/ofono/gatchat/gatresult.c index 2659db28c..883b4105f 100644 --- a/ofono/gatchat/gatresult.c +++ b/ofono/gatchat/gatresult.c @@ -111,6 +111,7 @@ gboolean g_at_result_iter_next_unquoted_string(GAtResultIter *iter, unsigned int pos; unsigned int end; unsigned int len; + unsigned int stripped; char *line; if (iter == NULL) @@ -139,7 +140,12 @@ gboolean g_at_result_iter_next_unquoted_string(GAtResultIter *iter, while (end < len && line[end] != ',' && line[end] != ')') end += 1; - iter->buf[end] = '\0'; + stripped = end; + + while (line[stripped - 1] == ' ') + stripped -= 1; + + iter->buf[stripped] = '\0'; out: iter->line_pos = skip_to_next_field(line, end, len); diff --git a/ofono/gatchat/gatresult.h b/ofono/gatchat/gatresult.h index 589dd3dd6..e92d38ba0 100644 --- a/ofono/gatchat/gatresult.h +++ b/ofono/gatchat/gatresult.h @@ -22,6 +22,8 @@ #ifndef __GATCHAT_RESULT_H #define __GATCHAT_RESULT_H +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/ofono/gatchat/gatutil.c b/ofono/gatchat/gatutil.c index a2528e13a..be691b2aa 100644 --- a/ofono/gatchat/gatutil.c +++ b/ofono/gatchat/gatutil.c @@ -67,8 +67,7 @@ void g_at_util_debug_chat(gboolean in, const char *str, gsize len, escaped_str[0] = type; escaped_str[1] = ' '; - escaped_str[2] = '\0'; - escaped_str[escaped] = '\0'; + memset(escaped_str + 2, '\0', escaped - 1); for (escaped = 2, i = 0; i < len; i++) { unsigned char c = str[i]; @@ -87,11 +86,11 @@ void g_at_util_debug_chat(gboolean in, const char *str, gsize len, escaped_str[escaped++] = 'n'; break; case 26: - strncpy(&escaped_str[escaped], ctrlz, ctrlz_size); + memcpy(escaped_str + escaped, ctrlz, ctrlz_size); escaped += ctrlz_size; break; case 25: - strncpy(&escaped_str[escaped], esc, esc_size); + memcpy(escaped_str + escaped, esc, esc_size); escaped += esc_size; break; default: diff --git a/ofono/gisi/modem.c b/ofono/gisi/modem.c index ef0c04911..894235983 100644 --- a/ofono/gisi/modem.c +++ b/ofono/gisi/modem.c @@ -33,6 +33,9 @@ #include #include +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + #include "message.h" #include "common.h" #include "modem.h" diff --git a/ofono/gisi/socket.c b/ofono/gisi/socket.c index 2428f5d58..017bbb319 100644 --- a/ofono/gisi/socket.c +++ b/ofono/gisi/socket.c @@ -32,6 +32,9 @@ #include #include +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + #include "phonet.h" #include "socket.h" diff --git a/ofono/include/gprs-context.h b/ofono/include/gprs-context.h index b77826221..b733d25da 100644 --- a/ofono/include/gprs-context.h +++ b/ofono/include/gprs-context.h @@ -93,6 +93,8 @@ void ofono_gprs_context_set_type(struct ofono_gprs_context *gc, enum ofono_gprs_context_type ofono_gprs_context_get_type( struct ofono_gprs_context *gc); +const char *ofono_gprs_context_get_interface(struct ofono_gprs_context *gc); + void ofono_gprs_context_set_interface(struct ofono_gprs_context *gc, const char *interface); diff --git a/ofono/include/gprs.h b/ofono/include/gprs.h index c8d8c9659..7ff0bd471 100644 --- a/ofono/include/gprs.h +++ b/ofono/include/gprs.h @@ -45,6 +45,8 @@ struct ofono_gprs_driver { ofono_gprs_cb_t cb, void *data); void (*attached_status)(struct ofono_gprs *gprs, ofono_gprs_status_cb_t cb, void *data); + void (*list_active_contexts)(struct ofono_gprs *gprs, + ofono_gprs_cb_t cb, void *data); }; enum gprs_suspend_cause { diff --git a/ofono/include/netmon.h b/ofono/include/netmon.h index c8fcafa62..a99d6ca92 100644 --- a/ofono/include/netmon.h +++ b/ofono/include/netmon.h @@ -43,6 +43,8 @@ struct ofono_netmon_driver { unsigned int enable, unsigned int period, ofono_netmon_cb_t cb, void *data); + void (*neighbouring_cell_update)(struct ofono_netmon *netmon, + ofono_netmon_cb_t cb, void *data); }; enum ofono_netmon_cell_type { @@ -104,6 +106,10 @@ void ofono_netmon_set_data(struct ofono_netmon *netmon, void *data); void *ofono_netmon_get_data(struct ofono_netmon *netmon); +void ofono_netmon_neighbouring_cell_notify(struct ofono_netmon *netmon, + enum ofono_netmon_cell_type type, + int info_type, ...); + #ifdef __cplusplus } #endif diff --git a/ofono/include/sim.h b/ofono/include/sim.h index 3bce459fa..9f5dee5c4 100644 --- a/ofono/include/sim.h +++ b/ofono/include/sim.h @@ -138,6 +138,10 @@ typedef void (*ofono_sim_close_channel_cb_t)(const struct ofono_error *error, typedef void (*ofono_sim_logical_access_cb_t)(const struct ofono_error *error, const unsigned char *resp, unsigned int len, void *data); +typedef void (*ofono_sim_set_active_card_slot_cb_t)( + const struct ofono_error *error, + void *data); + struct ofono_sim_driver { const char *name; int (*probe)(struct ofono_sim *sim, unsigned int vendor, void *data); @@ -211,7 +215,9 @@ struct ofono_sim_driver { void (*logical_access)(struct ofono_sim *sim, int session_id, const unsigned char *pdu, unsigned int len, ofono_sim_logical_access_cb_t cb, void *data); - /* Since mer/1.23+git28 */ + void (*set_active_card_slot)(struct ofono_sim *sim, unsigned int index, + ofono_sim_set_active_card_slot_cb_t cb, void *data); + /* Since mer/1.23+git28 */ void (*open_channel2)(struct ofono_sim *sim, const unsigned char *aid, unsigned int len, ofono_sim_open_channel_cb_t cb, void *data); @@ -229,6 +235,9 @@ void ofono_sim_remove(struct ofono_sim *sim); void ofono_sim_set_data(struct ofono_sim *sim, void *data); void *ofono_sim_get_data(struct ofono_sim *sim); +void ofono_sim_set_card_slot_count(struct ofono_sim *sim, unsigned int val); +void ofono_sim_set_active_card_slot(struct ofono_sim *sim, + unsigned int val); const char *ofono_sim_get_imsi(struct ofono_sim *sim); const char *ofono_sim_get_mcc(struct ofono_sim *sim); diff --git a/ofono/include/types.h b/ofono/include/types.h index fc2021630..0cb4d1031 100644 --- a/ofono/include/types.h +++ b/ofono/include/types.h @@ -55,7 +55,9 @@ enum ofono_access_technology { OFONO_ACCESS_TECHNOLOGY_UTRAN_HSDPA = 4, OFONO_ACCESS_TECHNOLOGY_UTRAN_HSUPA = 5, OFONO_ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA = 6, - OFONO_ACCESS_TECHNOLOGY_EUTRAN = 7 + OFONO_ACCESS_TECHNOLOGY_EUTRAN = 7, + OFONO_ACCESS_TECHNOLOGY_NB_IOT_M1 = 8, + OFONO_ACCESS_TECHNOLOGY_NB_IOT_NB1 = 9 }; /* 27.007 Section 6.2 */ diff --git a/ofono/plugins/alcatel.c b/ofono/plugins/alcatel.c index fb1d1ae57..8da06192a 100644 --- a/ofono/plugins/alcatel.c +++ b/ofono/plugins/alcatel.c @@ -91,34 +91,7 @@ static void alcatel_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GIOChannel *channel; - GAtSyntax *syntax; - GAtChat *chat; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, alcatel_debug, debug); - - return chat; + return at_util_open_device(modem, key, alcatel_debug, debug, NULL); } static void sim_state_cb(gboolean present, gpointer user_data) diff --git a/ofono/plugins/hfp_ag_bluez5.c b/ofono/plugins/hfp_ag_bluez5.c index 7653c4df2..edba5666d 100644 --- a/ofono/plugins/hfp_ag_bluez5.c +++ b/ofono/plugins/hfp_ag_bluez5.c @@ -38,9 +38,6 @@ #include #include -typedef struct GAtChat GAtChat; -typedef struct GAtResult GAtResult; - #include "drivers/atmodem/atutil.h" #include "hfp.h" diff --git a/ofono/plugins/hso.c b/ofono/plugins/hso.c index 249bb2caa..b58bf2a53 100644 --- a/ofono/plugins/hso.c +++ b/ofono/plugins/hso.c @@ -301,34 +301,7 @@ static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GIOChannel *channel; - GAtSyntax *syntax; - GAtChat *chat; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, hso_debug, debug); - - return chat; + return at_util_open_device(modem, key, hso_debug, debug, NULL); } static int hso_enable(struct ofono_modem *modem) diff --git a/ofono/plugins/huawei.c b/ofono/plugins/huawei.c index 817ee66ae..bdf7bc397 100644 --- a/ofono/plugins/huawei.c +++ b/ofono/plugins/huawei.c @@ -553,36 +553,15 @@ static void gcap_support(gboolean ok, GAtResult *result, gpointer user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GIOChannel *channel; - GAtSyntax *syntax; - GAtChat *chat; + GAtChat *chat = at_util_open_device(modem, key, huawei_debug, debug, + NULL); - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) + if (!chat) return NULL; g_at_chat_add_terminator(chat, "COMMAND NOT SUPPORT", -1, FALSE); g_at_chat_add_terminator(chat, "TOO MANY PARAMETERS", -1, FALSE); - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, huawei_debug, debug); - return chat; } diff --git a/ofono/plugins/icera.c b/ofono/plugins/icera.c index 7df5ffd76..90aca221b 100644 --- a/ofono/plugins/icera.c +++ b/ofono/plugins/icera.c @@ -96,42 +96,9 @@ static void icera_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - GAtChat *chat; - GAtSyntax *syntax; - GIOChannel *channel; - GHashTable *options; - const char *device; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - options = g_hash_table_new(g_str_hash, g_str_equal); - if (options == NULL) - return NULL; - - g_hash_table_insert(options, "Baud", "115200"); - - channel = g_at_tty_open(device, options); - - g_hash_table_destroy(options); - - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, icera_debug, debug); - - return chat; + return at_util_open_device(modem, key, icera_debug, debug, + "Baud", "115200", + NULL); } static void ussdmode_query(gboolean ok, GAtResult *result, diff --git a/ofono/plugins/linktop.c b/ofono/plugins/linktop.c index bb0d7b8ea..2b8acf769 100644 --- a/ofono/plugins/linktop.c +++ b/ofono/plugins/linktop.c @@ -91,34 +91,7 @@ static void linktop_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GIOChannel *channel; - GAtSyntax *syntax; - GAtChat *chat; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, linktop_debug, debug); - - return chat; + return at_util_open_device(modem, key, linktop_debug, debug, NULL); } static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) diff --git a/ofono/plugins/mbm.c b/ofono/plugins/mbm.c index b787aeb79..38297d747 100644 --- a/ofono/plugins/mbm.c +++ b/ofono/plugins/mbm.c @@ -285,35 +285,12 @@ static void emrdy_query(gboolean ok, GAtResult *result, gpointer user_data) cfun_query, modem, NULL); } -static GAtChat *create_port(const char *device) +static GAtChat *open_device(struct ofono_modem *modem, const char *key, + char *debug) { - GAtSyntax *syntax; - GIOChannel *channel; - GAtChat *chat; - GHashTable *options; - - options = g_hash_table_new(g_str_hash, g_str_equal); - if (options == NULL) - return NULL; - - g_hash_table_insert(options, "Baud", "115200"); - - channel = g_at_tty_open(device, options); - - g_hash_table_destroy(options); - - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - return chat; + return at_util_open_device(modem, key, mbm_debug, debug, + "Baud", "115200", + NULL); } static int mbm_enable(struct ofono_modem *modem) @@ -332,14 +309,11 @@ static int mbm_enable(struct ofono_modem *modem) if (modem_dev == NULL || data_dev == NULL) return -EINVAL; - data->modem_port = create_port(modem_dev); + data->modem_port = open_device(modem, "ModemDevice", "Modem: "); if (data->modem_port == NULL) return -EIO; - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(data->modem_port, mbm_debug, "Modem: "); - - data->data_port = create_port(data_dev); + data->data_port = open_device(modem, "DataDevice", "Data: "); if (data->data_port == NULL) { g_at_chat_unref(data->modem_port); data->modem_port = NULL; @@ -347,9 +321,6 @@ static int mbm_enable(struct ofono_modem *modem) return -EIO; } - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(data->data_port, mbm_debug, "Data: "); - g_at_chat_register(data->modem_port, "*EMRDY:", emrdy_notifier, FALSE, modem, NULL); diff --git a/ofono/plugins/nokia.c b/ofono/plugins/nokia.c index ef598fa4b..7f19ce968 100644 --- a/ofono/plugins/nokia.c +++ b/ofono/plugins/nokia.c @@ -43,6 +43,7 @@ #include #include +#include #include static const char *none_prefix[] = { NULL }; @@ -91,34 +92,7 @@ static void nokia_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GAtSyntax *syntax; - GIOChannel *channel; - GAtChat *chat; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, nokia_debug, debug); - - return chat; + return at_util_open_device(modem, key, nokia_debug, debug, NULL); } static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) diff --git a/ofono/plugins/novatel.c b/ofono/plugins/novatel.c index a64364d16..c01aa5485 100644 --- a/ofono/plugins/novatel.c +++ b/ofono/plugins/novatel.c @@ -96,32 +96,7 @@ static void novatel_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - GAtChat *chat; - GAtSyntax *syntax; - GIOChannel *channel; - const char *device; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, novatel_debug, debug); - - return chat; + return at_util_open_device(modem, key, novatel_debug, debug, NULL); } static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) diff --git a/ofono/plugins/phonesim.c b/ofono/plugins/phonesim.c index 7f17c2d33..2209a35bd 100644 --- a/ofono/plugins/phonesim.c +++ b/ofono/plugins/phonesim.c @@ -161,9 +161,7 @@ static void phonesim_activate_primary(struct ofono_gprs_context *gc, break; } - if (ctx->apn) - snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", - ctx->apn); + snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", ctx->apn); /* Assume always succeeds */ if (g_at_chat_send(gcd->chat, buf, none_prefix, NULL, NULL, NULL) == 0) diff --git a/ofono/plugins/quectel.c b/ofono/plugins/quectel.c index ba1aa42da..80beb921d 100644 --- a/ofono/plugins/quectel.c +++ b/ofono/plugins/quectel.c @@ -25,35 +25,106 @@ #include #include +#include +#include #include +#include +#include +#include +#include +#include #include #include +#include #define OFONO_API_SUBJECT_TO_CHANGE +#include #include #include #include #include #include +#include +#include +#include +#include #include #include #include +#include + +#include #include #include static const char *cfun_prefix[] = { "+CFUN:", NULL }; static const char *cpin_prefix[] = { "+CPIN:", NULL }; +static const char *cbc_prefix[] = { "+CBC:", NULL }; +static const char *qinistat_prefix[] = { "+QINISTAT:", NULL }; +static const char *cgmm_prefix[] = { "UC15", "Quectel_M95", "Quectel_MC60", + NULL }; static const char *none_prefix[] = { NULL }; +static const uint8_t gsm0710_terminate[] = { + 0xf9, /* open flag */ + 0x03, /* channel 0 */ + 0xef, /* UIH frame */ + 0x05, /* 2 data bytes */ + 0xc3, /* terminate 1 */ + 0x01, /* terminate 2 */ + 0xf2, /* crc */ + 0xf9, /* close flag */ +}; + +enum quectel_model { + QUECTEL_UNKNOWN, + QUECTEL_UC15, + QUECTEL_M95, + QUECTEL_MC60, +}; + struct quectel_data { GAtChat *modem; GAtChat *aux; - guint cpin_ready; - gboolean have_sim; + enum ofono_vendor vendor; + enum quectel_model model; + struct at_util_sim_state_query *sim_state_query; + unsigned int sim_watch; + bool sim_locked; + bool sim_ready; + + /* used by quectel uart driver */ + GIOChannel *device; + GAtChat *uart; + GAtMux *mux; + int mux_ready_count; + int initial_ldisc; + struct l_gpio_writer *gpio; + struct l_timeout *init_timeout; + size_t init_count; + guint init_cmd; +}; + +struct dbus_hw { + DBusMessage *msg; + struct ofono_modem *modem; + int32_t charge_status; + int32_t charge_level; + int32_t voltage; }; +enum quectel_power_event { + LOW_POWER_DOWN = -2, + LOW_WARNING = -1, + NORMAL_POWER_DOWN = 0, + HIGH_WARNING = 1, + HIGH_POWER_DOWN = 2, +}; + +static const char dbus_hw_interface[] = OFONO_SERVICE ".quectel.Hardware"; + static void quectel_debug(const char *str, void *user_data) { const char *prefix = user_data; @@ -61,6 +132,43 @@ static void quectel_debug(const char *str, void *user_data) ofono_info("%s%s", prefix, str); } +static int quectel_probe_gpio(struct ofono_modem *modem) +{ + struct quectel_data *data = ofono_modem_get_data(modem); + struct l_gpio_chip *gpiochip; + uint32_t offset; + const char *chip_name, *offset_str; + uint32_t value = 0; + + DBG("%p", modem); + + chip_name = ofono_modem_get_string(modem, "GpioChip"); + if (!chip_name) + return 0; + + offset_str = ofono_modem_get_string(modem, "GpioOffset"); + if (!offset_str) + return -EINVAL; + + offset = strtoul(offset_str, NULL, 0); + if (!offset) + return -EINVAL; + + gpiochip = l_gpio_chip_new(chip_name); + if (!gpiochip) + return -ENODEV; + + data->gpio = l_gpio_writer_new(gpiochip, "ofono", 1, &offset, + &value); + + l_gpio_chip_free(gpiochip); + + if (!data->gpio) + return -EIO; + + return 0; +} + static int quectel_probe(struct ofono_modem *modem) { struct quectel_data *data; @@ -73,7 +181,7 @@ static int quectel_probe(struct ofono_modem *modem) ofono_modem_set_data(modem, data); - return 0; + return quectel_probe_gpio(modem); } static void quectel_remove(struct ofono_modem *modem) @@ -82,54 +190,421 @@ static void quectel_remove(struct ofono_modem *modem) DBG("%p", modem); - if (data->cpin_ready != 0) - g_at_chat_unregister(data->aux, data->cpin_ready); - ofono_modem_set_data(modem, NULL); + l_timeout_remove(data->init_timeout); + l_gpio_writer_free(data->gpio); + at_util_sim_state_query_free(data->sim_state_query); g_at_chat_unref(data->aux); g_at_chat_unref(data->modem); + g_at_chat_unref(data->uart); + g_at_mux_unref(data->mux); + + if (data->device) + g_io_channel_unref(data->device); + g_free(data); } -static GAtChat *open_device(struct ofono_modem *modem, - const char *key, char *debug) +static void close_mux(struct ofono_modem *modem) { - const char *device; - GAtSyntax *syntax; - GIOChannel *channel; - GAtChat *chat; + struct quectel_data *data = ofono_modem_get_data(modem); - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; + DBG("%p", modem); - DBG("%s %s", key, device); + g_io_channel_unref(data->device); + data->device = NULL; - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; + g_at_mux_unref(data->mux); + data->mux = NULL; +} - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); +static void close_ngsm(struct ofono_modem *modem) +{ + struct quectel_data *data = ofono_modem_get_data(modem); + int fd; - g_io_channel_unref(channel); + DBG("%p", modem); - if (chat == NULL) - return NULL; + if (!data->device) + return; - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, quectel_debug, debug); + fd = g_io_channel_unix_get_fd(data->device); - return chat; + /* restore initial tty line discipline */ + if (ioctl(fd, TIOCSETD, &data->initial_ldisc) < 0) + ofono_warn("Failed to restore line discipline"); +} + +static void close_serial(struct ofono_modem *modem) +{ + struct quectel_data *data = ofono_modem_get_data(modem); + uint32_t gpio_value = 0; + + DBG("%p", modem); + + at_util_sim_state_query_free(data->sim_state_query); + data->sim_state_query = NULL; + + g_at_chat_unref(data->aux); + data->aux = NULL; + + g_at_chat_unref(data->modem); + data->modem = NULL; + + g_at_chat_unref(data->uart); + data->uart = NULL; + + if (data->mux) + close_mux(modem); + else + close_ngsm(modem); + + l_gpio_writer_set(data->gpio, 1, &gpio_value); + ofono_modem_set_powered(modem, FALSE); +} + +static void dbus_hw_reply_properties(struct dbus_hw *hw) +{ + struct quectel_data *data = ofono_modem_get_data(hw->modem); + DBusMessage *reply; + DBusMessageIter dbus_iter; + DBusMessageIter dbus_dict; + + DBG("%p", hw->modem); + + reply = dbus_message_new_method_return(hw->msg); + dbus_message_iter_init_append(reply, &dbus_iter); + dbus_message_iter_open_container(&dbus_iter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dbus_dict); + + /* + * the charge status/level received from m95 and mc60 are invalid so + * only return those for the UC15 modem. + */ + if (data->model == QUECTEL_UC15) { + ofono_dbus_dict_append(&dbus_dict, "ChargeStatus", + DBUS_TYPE_INT32, &hw->charge_status); + + ofono_dbus_dict_append(&dbus_dict, "ChargeLevel", + DBUS_TYPE_INT32, &hw->charge_level); + } + + ofono_dbus_dict_append(&dbus_dict, "Voltage", DBUS_TYPE_INT32, + &hw->voltage); + + dbus_message_iter_close_container(&dbus_iter, &dbus_dict); + + __ofono_dbus_pending_reply(&hw->msg, reply); +} + +static void cbc_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct dbus_hw *hw = user_data; + GAtResultIter iter; + + DBG("%p", hw->modem); + + if (!hw->msg) + return; + + if (!ok) + goto error; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CBC:")) + goto error; + + /* the returned charge status is valid only for uc15 */ + if (!g_at_result_iter_next_number(&iter, &hw->charge_status)) + goto error; + + /* the returned charge level is valid only for uc15 */ + if (!g_at_result_iter_next_number(&iter, &hw->charge_level)) + goto error; + + /* now comes the millivolts */ + if (!g_at_result_iter_next_number(&iter, &hw->voltage)) + goto error; + + dbus_hw_reply_properties(hw); + + return; + +error: + __ofono_dbus_pending_reply(&hw->msg, __ofono_error_failed(hw->msg)); +} + +static DBusMessage *dbus_hw_get_properties(DBusConnection *conn, + DBusMessage *msg, + void *user_data) +{ + struct dbus_hw *hw = user_data; + struct quectel_data *data = ofono_modem_get_data(hw->modem); + + DBG("%p", hw->modem); + + if (hw->msg != NULL) + return __ofono_error_busy(msg); + + if (!g_at_chat_send(data->aux, "AT+CBC", cbc_prefix, cbc_cb, hw, NULL)) + return __ofono_error_failed(msg); + + hw->msg = dbus_message_ref(msg); + + return NULL; +} + +static void voltage_handle(struct ofono_modem *modem, + enum quectel_power_event event) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + DBusMessage *signal; + DBusMessageIter iter; + const char *path = ofono_modem_get_path(modem); + const char *name; + const char *reason; + bool close; + + DBG("%p", modem); + + switch (event) { + case LOW_POWER_DOWN: + close = true; + name = "PowerDown"; + reason = "voltagelow"; + break; + case LOW_WARNING: + close = false; + name = "PowerWarning"; + reason = "voltagelow"; + break; + case NORMAL_POWER_DOWN: + close = true; + name = "PowerDown"; + reason = "normal"; + break; + case HIGH_WARNING: + close = false; + name = "PowerWarning"; + reason = "voltagehigh"; + break; + case HIGH_POWER_DOWN: + close = true; + name = "PowerDown"; + reason = "voltagehigh"; + break; + default: + return; + } + + signal = dbus_message_new_signal(path, dbus_hw_interface, name); + if (signal) { + dbus_message_iter_init_append(signal, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, + &reason); + g_dbus_send_message(conn, signal); + } + + if (close) + close_serial(modem); +} + +static void qind_notify(GAtResult *result, void *user_data) +{ + struct dbus_hw *hw = user_data; + GAtResultIter iter; + enum quectel_power_event event; + const char *type; + + DBG("%p", hw->modem); + + g_at_result_iter_init(&iter, result); + g_at_result_iter_next(&iter, "+QIND:"); + + if (!g_at_result_iter_next_string(&iter, &type)) + return; + + if (!g_at_result_iter_next_number(&iter, &event)) + return; + + voltage_handle(hw->modem, event); +} + +static void power_notify(GAtResult *result, void *user_data) +{ + struct dbus_hw *hw = user_data; + GAtResultIter iter; + const char *event; + + DBG("%p", hw->modem); + + g_at_result_iter_init(&iter, result); + g_at_result_iter_next(&iter, NULL); + + if (!g_at_result_iter_next_unquoted_string(&iter, &event)) + return; + + DBG("event: %s", event); + + if (g_strcmp0(event, "UNDER_VOLTAGE POWER DOWN") == 0) + voltage_handle(hw->modem, LOW_POWER_DOWN); + else if (g_strcmp0(event, "UNDER_VOLTAGE WARNING") == 0) + voltage_handle(hw->modem, LOW_WARNING); + else if (g_strcmp0(event, "NORMAL POWER DOWN") == 0) + voltage_handle(hw->modem, NORMAL_POWER_DOWN); + else if (g_strcmp0(event, "OVER_VOLTAGE WARNING") == 0) + voltage_handle(hw->modem, HIGH_WARNING); + else if (g_strcmp0(event, "OVER_VOLTAGE POWER DOWN") == 0) + voltage_handle(hw->modem, HIGH_POWER_DOWN); +} + +static const GDBusMethodTable dbus_hw_methods[] = { + { GDBUS_ASYNC_METHOD("GetProperties", + NULL, GDBUS_ARGS({ "properties", "a{sv}" }), + dbus_hw_get_properties) }, + {} +}; + +static const GDBusSignalTable dbus_hw_signals[] = { + { GDBUS_SIGNAL("PowerDown", + GDBUS_ARGS({ "reason", "s" })) }, + { GDBUS_SIGNAL("PowerWarning", + GDBUS_ARGS({ "reason", "s" })) }, + { } +}; + +static void dbus_hw_cleanup(void *data) +{ + struct dbus_hw *hw = data; + + DBG("%p", hw->modem); + + if (hw->msg) + __ofono_dbus_pending_reply(&hw->msg, + __ofono_error_canceled(hw->msg)); + + l_free(hw); +} + +static void dbus_hw_enable(struct ofono_modem *modem) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + struct quectel_data *data = ofono_modem_get_data(modem); + const char *path = ofono_modem_get_path(modem); + struct dbus_hw *hw; + + DBG("%p", modem); + + hw = l_new(struct dbus_hw, 1); + hw->modem = modem; + + if (!g_dbus_register_interface(conn, path, dbus_hw_interface, + dbus_hw_methods, dbus_hw_signals, NULL, + hw, dbus_hw_cleanup)) { + ofono_error("Could not register %s interface under %s", + dbus_hw_interface, path); + l_free(hw); + return; + } + + g_at_chat_register(data->aux, "NORMAL POWER DOWN", power_notify, FALSE, + hw, NULL); + + switch (data->model) { + case QUECTEL_UC15: + g_at_chat_register(data->aux, "+QIND", qind_notify, FALSE, hw, + NULL); + break; + case QUECTEL_M95: + case QUECTEL_MC60: + g_at_chat_register(data->aux, "OVER_VOLTAGE POWER DOWN", + power_notify, FALSE, hw, NULL); + g_at_chat_register(data->aux, "UNDER_VOLTAGE POWER DOWN", + power_notify, FALSE, hw, NULL); + g_at_chat_register(data->aux, "OVER_VOLTAGE WARNING", + power_notify, FALSE, hw, NULL); + g_at_chat_register(data->aux, "UNDER_VOLTAGE WARNING", + power_notify, FALSE, hw, NULL); + break; + case QUECTEL_UNKNOWN: + break; + } + + ofono_modem_add_interface(modem, dbus_hw_interface); +} + +static void qinistat_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct ofono_sim *sim = ofono_modem_get_sim(modem); + struct quectel_data *data = ofono_modem_get_data(modem); + GAtResultIter iter; + int ready = 0; + int status; + + DBG("%p", modem); + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+QINISTAT:")) + return; + + if (!g_at_result_iter_next_number(&iter, &status)) + return; + + DBG("qinistat: %d", status); + + switch (data->model) { + case QUECTEL_UC15: + /* UC15 uses a bitmap of 1 + 2 + 4 = 7 */ + ready = 7; + break; + case QUECTEL_M95: + case QUECTEL_MC60: + /* M95 and MC60 uses a counter to 3 */ + ready = 3; + break; + case QUECTEL_UNKNOWN: + ready = 0; + break; + } + + if (status != ready) { + l_timeout_modify_ms(data->init_timeout, 500); + return; + } + + l_timeout_remove(data->init_timeout); + data->init_timeout = NULL; + + if (data->sim_locked) { + ofono_sim_initialized_notify(sim); + return; + } + + data->sim_ready = true; + ofono_modem_set_powered(modem, TRUE); } -static void cpin_notify(GAtResult *result, gpointer user_data) +static void init_timer_cb(struct l_timeout *timeout, void *user_data) +{ + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + + DBG("%p", modem); + + g_at_chat_send(data->aux, "AT+QINISTAT", qinistat_prefix, qinistat_cb, + modem, NULL); +} + +static void sim_watch_cb(GAtResult *result, void *user_data) { struct ofono_modem *modem = user_data; struct quectel_data *data = ofono_modem_get_data(modem); - const char *sim_inserted; GAtResultIter iter; + const char *cpin; DBG("%p", modem); @@ -138,26 +613,99 @@ static void cpin_notify(GAtResult *result, gpointer user_data) if (!g_at_result_iter_next(&iter, "+CPIN:")) return; - g_at_result_iter_next_unquoted_string(&iter, &sim_inserted); + g_at_result_iter_next_unquoted_string(&iter, &cpin); + + if (g_strcmp0(cpin, "READY") != 0) + return; + + g_at_chat_unregister(data->aux, data->sim_watch); + data->sim_watch = 0; + + data->init_timeout = l_timeout_create_ms(500, init_timer_cb, modem, NULL); +} + +static void cpin_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + const char *path = ofono_modem_get_path(modem); + GAtResultIter iter; + const char *cpin; + + DBG("%p", modem); + + if (!ok) { + close_serial(modem); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CPIN:")) { + close_serial(modem); + return; + } + + g_at_result_iter_next_unquoted_string(&iter, &cpin); - if (g_strcmp0(sim_inserted, "NOT INSERTED") != 0) - data->have_sim = TRUE; + if (g_strcmp0(cpin, "READY") == 0) { + data->init_timeout = l_timeout_create_ms(500, init_timer_cb, + modem, NULL); + return; + } + if (g_strcmp0(cpin, "SIM PIN") != 0) { + close_serial(modem); + return; + } + + ofono_info("%s: sim locked", path); + data->sim_locked = true; + data->sim_watch = g_at_chat_register(data->aux, "+CPIN:", + sim_watch_cb, FALSE, + modem, NULL); ofono_modem_set_powered(modem, TRUE); +} - /* Turn off the radio. */ - g_at_chat_send(data->aux, "AT+CFUN=4", none_prefix, NULL, NULL, NULL); +static void sim_state_cb(gboolean present, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + const char *path = ofono_modem_get_path(modem); + + DBG("%p present %d", modem, present); + + at_util_sim_state_query_free(data->sim_state_query); + data->sim_state_query = NULL; + data->sim_locked = false; + data->sim_ready = false; + + if (!present) { + ofono_modem_set_powered(modem, TRUE); + ofono_warn("%s: sim not present", path); + return; + } - g_at_chat_unregister(data->aux, data->cpin_ready); - data->cpin_ready = 0; + g_at_chat_send(data->aux, "AT+CPIN?", cpin_prefix, cpin_cb, modem, + NULL); } -static void cpin_query(gboolean ok, GAtResult *result, gpointer user_data) +static void cfun_cb(gboolean ok, GAtResult *result, gpointer user_data) { - DBG("ok %d", ok); + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + + DBG("%p ok %d", modem, ok); + + if (!ok) { + close_serial(modem); + return; + } - if (ok) - cpin_notify(result, user_data); + dbus_hw_enable(modem); + data->sim_state_query = at_util_sim_state_query_new(data->aux, + 2, 20, sim_state_cb, modem, + NULL); } static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) @@ -165,21 +713,15 @@ static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) struct ofono_modem *modem = user_data; struct quectel_data *data = ofono_modem_get_data(modem); - DBG("ok %d", ok); + DBG("%p ok %d", modem, ok); if (!ok) { - g_at_chat_unref(data->aux); - data->aux = NULL; - g_at_chat_unref(data->modem); - data->modem = NULL; - ofono_modem_set_powered(modem, FALSE); + close_serial(modem); return; } - data->cpin_ready = g_at_chat_register(data->aux, "+CPIN", cpin_notify, - FALSE, modem, NULL); - g_at_chat_send(data->aux, "AT+CPIN?", cpin_prefix, cpin_query, - modem, NULL); + g_at_chat_send(data->aux, "AT+CFUN=4", none_prefix, cfun_cb, modem, + NULL); } static void cfun_query(gboolean ok, GAtResult *result, gpointer user_data) @@ -187,19 +729,23 @@ static void cfun_query(gboolean ok, GAtResult *result, gpointer user_data) struct ofono_modem *modem = user_data; struct quectel_data *data = ofono_modem_get_data(modem); GAtResultIter iter; - int status; + int cfun; - DBG("ok %d", ok); + DBG("%p ok %d", modem, ok); - if (!ok) + if (!ok) { + close_serial(modem); return; + } g_at_result_iter_init(&iter, result); - if (g_at_result_iter_next(&iter, "+CFUN:") == FALSE) + if (g_at_result_iter_next(&iter, "+CFUN:") == FALSE) { + close_serial(modem); return; + } - g_at_result_iter_next_number(&iter, &status); + g_at_result_iter_next_number(&iter, &cfun); /* * The modem firmware powers up in CFUN=1 but will respond to AT+CFUN=4 @@ -207,80 +753,442 @@ static void cfun_query(gboolean ok, GAtResult *result, gpointer user_data) * passes. Empirical evidence suggests that the firmware will report an * unsolicited +CPIN: notification when it is ready to be useful. * - * Work around this feature by only transitioning to CFUN=4 after we've - * received an unsolicited +CPIN: notification. + * Work around this feature by only transitioning to CFUN=4 if the + * modem is not in CFUN=1 or until after we've received an unsolicited + * +CPIN: notification. */ + if (cfun != 1) + g_at_chat_send(data->aux, "AT+CFUN=4", none_prefix, cfun_enable, + modem, NULL); + else + cfun_enable(TRUE, NULL, modem); +} + +static void cgmm_cb(int ok, GAtResult *result, void *user_data) +{ + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + const char *model; - if (status != 1) { - g_at_chat_send(data->aux, "AT+CFUN=4", none_prefix, - cfun_enable, modem, NULL); + DBG("%p ok %d", modem, ok); + + if (!at_util_parse_attr(result, "", &model)) { + ofono_error("Failed to query modem model"); + close_serial(modem); return; } - cfun_enable(TRUE, NULL, modem); + if (strcmp(model, "UC15") == 0) { + DBG("%p model UC15", modem); + data->vendor = OFONO_VENDOR_QUECTEL; + data->model = QUECTEL_UC15; + } else if (strcmp(model, "Quectel_M95") == 0) { + DBG("%p model M95", modem); + data->vendor = OFONO_VENDOR_QUECTEL_SERIAL; + data->model = QUECTEL_M95; + } else if (strcmp(model, "Quectel_MC60") == 0) { + DBG("%p model MC60", modem); + data->vendor = OFONO_VENDOR_QUECTEL_SERIAL; + data->model = QUECTEL_MC60; + } else { + ofono_warn("%p unknown model: '%s'", modem, model); + data->vendor = OFONO_VENDOR_QUECTEL; + data->model = QUECTEL_UNKNOWN; + } + + g_at_chat_send(data->aux, "AT+CFUN?", cfun_prefix, cfun_query, modem, + NULL); } -static int quectel_enable(struct ofono_modem *modem) +static void setup_aux(struct ofono_modem *modem) { struct quectel_data *data = ofono_modem_get_data(modem); DBG("%p", modem); - data->modem = open_device(modem, "Modem", "Modem: "); + g_at_chat_set_slave(data->modem, data->aux); + g_at_chat_send(data->aux, "ATE0; &C0; +CMEE=1; +QIURC=0", none_prefix, + NULL, NULL, NULL); + g_at_chat_send(data->aux, "AT+CGMM", cgmm_prefix, cgmm_cb, modem, + NULL); +} + +static int open_ttys(struct ofono_modem *modem) +{ + struct quectel_data *data = ofono_modem_get_data(modem); + + DBG("%p", modem); + + data->modem = at_util_open_device(modem, "Modem", quectel_debug, + "Modem: ", NULL); if (data->modem == NULL) return -EINVAL; - data->aux = open_device(modem, "Aux", "Aux: "); + data->aux = at_util_open_device(modem, "Aux", quectel_debug, "Aux: ", + NULL); if (data->aux == NULL) { g_at_chat_unref(data->modem); data->modem = NULL; return -EIO; } - g_at_chat_set_slave(data->modem, data->aux); + setup_aux(modem); + + return -EINPROGRESS; +} + +static GAtChat *create_chat(struct ofono_modem *modem, char *debug) +{ + struct quectel_data *data = ofono_modem_get_data(modem); + GIOChannel *channel; + GAtSyntax *syntax; + GAtChat *chat; - g_at_chat_send(data->modem, "ATE0 &C0 +CMEE=1", none_prefix, - NULL, NULL, NULL); - g_at_chat_send(data->aux, "ATE0 &C0 +CMEE=1", none_prefix, - NULL, NULL, NULL); + DBG("%p", modem); - g_at_chat_send(data->aux, "AT+CFUN?", cfun_prefix, - cfun_query, modem, NULL); + channel = g_at_mux_create_channel(data->mux); + if (channel == NULL) + return NULL; - return -EINPROGRESS; + syntax = g_at_syntax_new_gsmv1(); + chat = g_at_chat_new(channel, syntax); + g_at_syntax_unref(syntax); + g_io_channel_unref(channel); + + if (chat == NULL) + return NULL; + + if (getenv("OFONO_AT_DEBUG")) + g_at_chat_set_debug(chat, quectel_debug, debug); + + return chat; } -static void cfun_disable(gboolean ok, GAtResult *result, gpointer user_data) +static void cmux_gatmux(struct ofono_modem *modem) +{ + struct quectel_data *data = ofono_modem_get_data(modem); + + DBG("%p", modem); + + data->mux = g_at_mux_new_gsm0710_basic(data->device, 127); + if (data->mux == NULL) { + ofono_error("failed to create gsm0710 mux"); + close_serial(modem); + return; + } + + if (getenv("OFONO_MUX_DEBUG")) + g_at_mux_set_debug(data->mux, quectel_debug, "Mux: "); + + g_at_mux_start(data->mux); + + data->modem = create_chat(modem, "Modem: "); + if (!data->modem) { + ofono_error("failed to create modem channel"); + close_serial(modem); + return; + } + + data->aux = create_chat(modem, "Aux: "); + if (!data->aux) { + ofono_error("failed to create aux channel"); + close_serial(modem); + return; + } + + setup_aux(modem); +} + +static void mux_ready_cb(struct l_timeout *timeout, void *user_data) { struct ofono_modem *modem = user_data; struct quectel_data *data = ofono_modem_get_data(modem); + struct stat st; + int ret; - DBG(""); + DBG("%p", modem); - g_at_chat_unref(data->aux); - data->aux = NULL; + /* check if the last (and thus all) virtual gsm tty's are created */ + ret = stat(ofono_modem_get_string(modem, "Modem"), &st); + if (ret < 0) { + if (data->mux_ready_count++ < 5) { + /* not ready yet; try again in 100 ms*/ + l_timeout_modify_ms(timeout, 100); + return; + } + + /* not ready after 500 ms; bail out */ + close_serial(modem); + return; + } + + /* virtual gsm tty's are ready */ + l_timeout_remove(timeout); + + if (open_ttys(modem) != -EINPROGRESS) + close_serial(modem); + + g_at_chat_set_slave(data->uart, data->modem); +} + +static void cmux_ngsm(struct ofono_modem *modem) +{ + struct quectel_data *data = ofono_modem_get_data(modem); + struct gsm_config gsm_config; + int ldisc = N_GSM0710; + int fd; + + DBG("%p", modem); + + fd = g_io_channel_unix_get_fd(data->device); + + /* get initial line discipline to restore after use */ + if (ioctl(fd, TIOCGETD, &data->initial_ldisc) < 0) { + ofono_error("Failed to get current line discipline: %s", + strerror(errno)); + close_serial(modem); + return; + } + + /* enable gsm 0710 multiplexing line discipline */ + if (ioctl(fd, TIOCSETD, &ldisc) < 0) { + ofono_error("Failed to set multiplexer line discipline: %s", + strerror(errno)); + close_serial(modem); + return; + } + + /* get n_gsm configuration */ + if (ioctl(fd, GSMIOC_GETCONF, &gsm_config) < 0) { + ofono_error("Failed to get gsm config: %s", strerror(errno)); + close_serial(modem); + return; + } + + gsm_config.initiator = 1; /* cpu side is initiating multiplexing */ + gsm_config.encapsulation = 0; /* basic transparency encoding */ + gsm_config.mru = 127; /* 127 bytes rx mtu */ + gsm_config.mtu = 127; /* 127 bytes tx mtu */ + gsm_config.t1 = 10; /* 100 ms ack timer */ + gsm_config.n2 = 3; /* 3 retries */ + gsm_config.t2 = 30; /* 300 ms response timer */ + gsm_config.t3 = 10; /* 100 ms wake up response timer */ + gsm_config.i = 1; /* subset */ + + /* set the new configuration */ + if (ioctl(fd, GSMIOC_SETCONF, &gsm_config) < 0) { + ofono_error("Failed to set gsm config: %s", strerror(errno)); + close_serial(modem); + return; + } + + /* + * the kernel does not yet support mapping the underlying serial device + * to its virtual gsm ttys, so hard-code gsmtty1 gsmtty2 for now + */ + ofono_modem_set_string(modem, "Modem", "/dev/gsmtty1"); + ofono_modem_set_string(modem, "Aux", "/dev/gsmtty2"); + + /* wait for gsmtty devices to appear */ + if (!l_timeout_create_ms(100, mux_ready_cb, modem, NULL)) { + close_serial(modem); + return; + } +} + +static void cmux_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + const char *mux = ofono_modem_get_string(modem, "Mux"); + + DBG("%p", modem); + + g_at_chat_unref(data->uart); + data->uart = NULL; + + if (!ok) { + close_serial(modem); + return; + } + + if (!mux) + mux = "internal"; + + if (strcmp(mux, "n_gsm") == 0) { + cmux_ngsm(modem); + return; + } + + if (strcmp(mux, "internal") == 0) { + cmux_gatmux(modem); + return; + } + + ofono_error("unsupported mux setting: '%s'", mux); + close_serial(modem); +} + +static void ate_cb(int ok, GAtResult *result, void *user_data) +{ + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + + DBG("%p", modem); + + g_at_chat_send(data->uart, "AT+CMUX=0,0,5,127,10,3,30,10,2", NULL, + cmux_cb, modem, NULL); +} + +static void init_cmd_cb(gboolean ok, GAtResult *result, void *user_data) +{ + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + const char *rts_cts; + + DBG("%p", modem); + + if (!ok) + return; + + rts_cts = ofono_modem_get_string(modem, "RtsCts"); + + if (strcmp(rts_cts, "on") == 0) + g_at_chat_send(data->uart, "AT+IFC=2,2; E0", none_prefix, + ate_cb, modem, NULL); + else + g_at_chat_send(data->uart, "ATE0", none_prefix, ate_cb, modem, + NULL); + + l_timeout_remove(data->init_timeout); + data->init_timeout = NULL; +} + +static void init_timeout_cb(struct l_timeout *timeout, void *user_data) +{ + struct ofono_modem *modem = user_data; + struct quectel_data *data = ofono_modem_get_data(modem); + + DBG("%p", modem); - if (ok) - ofono_modem_set_powered(modem, FALSE); + if (data->init_count++ >= 20) { + ofono_error("failed to init modem after 20 attempts"); + close_serial(modem); + return; + } + + g_at_chat_retry(data->uart, data->init_cmd); + l_timeout_modify_ms(timeout, 500); +} + +static int open_serial(struct ofono_modem *modem) +{ + struct quectel_data *data = ofono_modem_get_data(modem); + const uint32_t gpio_value = 1; + const char *rts_cts; + ssize_t written; + int fd; + + DBG("%p", modem); + + rts_cts = ofono_modem_get_string(modem, "RtsCts"); + + data->uart = at_util_open_device(modem, "Device", quectel_debug, + "UART: ", + "Baud", "115200", + "Parity", "none", + "StopBits", "1", + "DataBits", "8", + "XonXoff", "off", + "Local", "on", + "Read", "on", + "RtsCts", rts_cts, + NULL); + if (data->uart == NULL) + return -EINVAL; + + data->device = g_at_chat_get_channel(data->uart); + g_io_channel_ref(data->device); + + /* + * terminate gsm 0710 multiplexing on the modem side to make sure it + * responds to plain AT commands + * */ + fd = g_io_channel_unix_get_fd(data->device); + written = write(fd, gsm0710_terminate, sizeof(gsm0710_terminate)); + if (written != sizeof(gsm0710_terminate)) + ofono_warn("Failed to terminate gsm multiplexing"); + + if (data->gpio && !l_gpio_writer_set(data->gpio, 1, &gpio_value)) { + close_serial(modem); + return -EIO; + } + + /* + * there are three different power-up scenarios: + * + * 1) the gpio has just been toggled on, so the modem is not ready + * until it prints RDY + * + * 2) the modem has been on for a while and ready to respond to + * commands, so there will be no RDY notification + * + * 3) either of the previous to scenarious is the case, but the modem + * UART is not configured to a fixed bitrate. In this case it needs + * a few 'AT' bytes to detect the host UART bitrate, but the RDY is + * lost. + * + * Handle all three cases by issuing a plain AT command. The modem + * answers with OK when it is ready. Create a timer to re-issue + * the AT command at regular intervals until the modem answers. + */ + data->init_count = 0; + data->init_cmd = g_at_chat_send(data->uart, "AT", none_prefix, + init_cmd_cb, modem, NULL); + data->init_timeout = l_timeout_create_ms(500, init_timeout_cb, modem, + NULL); + + return -EINPROGRESS; +} + +static int quectel_enable(struct ofono_modem *modem) +{ + DBG("%p", modem); + + if (ofono_modem_get_string(modem, "Device")) + return open_serial(modem); + else + return open_ttys(modem); +} + +static void cfun_disable(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + + DBG("%p", modem); + + close_serial(modem); } static int quectel_disable(struct ofono_modem *modem) { struct quectel_data *data = ofono_modem_get_data(modem); + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path = ofono_modem_get_path(modem); DBG("%p", modem); g_at_chat_cancel_all(data->modem); g_at_chat_unregister_all(data->modem); - g_at_chat_unref(data->modem); - data->modem = NULL; - g_at_chat_cancel_all(data->aux); g_at_chat_unregister_all(data->aux); - g_at_chat_send(data->aux, "AT+CFUN=0", cfun_prefix, - cfun_disable, modem, NULL); + if (g_dbus_unregister_interface(conn, path, dbus_hw_interface)) + ofono_modem_remove_interface(modem, dbus_hw_interface); + + g_at_chat_send(data->aux, "AT+CFUN=0", cfun_prefix, cfun_disable, modem, + NULL); return -EINPROGRESS; } @@ -291,6 +1199,8 @@ static void set_online_cb(gboolean ok, GAtResult *result, gpointer user_data) ofono_modem_online_cb_t cb = cbd->cb; struct ofono_error error; + DBG("%p", user_data); + decode_at_error(&error, g_at_result_final_response(result)); cb(&error, cbd->data); } @@ -304,8 +1214,8 @@ static void quectel_set_online(struct ofono_modem *modem, ofono_bool_t online, DBG("modem %p %s", modem, online ? "online" : "offline"); - if (g_at_chat_send(data->aux, command, cfun_prefix, set_online_cb, - cbd, g_free) > 0) + if (g_at_chat_send(data->aux, command, cfun_prefix, set_online_cb, cbd, + g_free) > 0) return; CALLBACK_WITH_FAILURE(cb, cbd->data); @@ -320,12 +1230,14 @@ static void quectel_pre_sim(struct ofono_modem *modem) DBG("%p", modem); - ofono_devinfo_create(modem, 0, "atmodem", data->aux); - sim = ofono_sim_create(modem, OFONO_VENDOR_QUECTEL, "atmodem", - data->aux); + ofono_voicecall_create(modem, data->vendor, "atmodem", data->aux); + sim = ofono_sim_create(modem, data->vendor, "atmodem", data->aux); + + if (data->sim_locked || data->sim_ready) + ofono_sim_inserted_notify(sim, true); - if (sim && data->have_sim == TRUE) - ofono_sim_inserted_notify(sim, TRUE); + if (data->sim_ready) + ofono_sim_initialized_notify(sim); } static void quectel_post_sim(struct ofono_modem *modem) @@ -336,17 +1248,24 @@ static void quectel_post_sim(struct ofono_modem *modem) DBG("%p", modem); - gprs = ofono_gprs_create(modem, 0, "atmodem", data->aux); - gc = ofono_gprs_context_create(modem, 0, "atmodem", data->modem); + gprs = ofono_gprs_create(modem, data->vendor, "atmodem", data->aux); + gc = ofono_gprs_context_create(modem, data->vendor, "atmodem", + data->modem); if (gprs && gc) ofono_gprs_add_context(gprs, gc); + + ofono_sms_create(modem, data->vendor, "atmodem", data->aux); + ofono_phonebook_create(modem, data->vendor, "atmodem", data->aux); + ofono_call_volume_create(modem, data->vendor, "atmodem", data->aux); } static void quectel_post_online(struct ofono_modem *modem) { struct quectel_data *data = ofono_modem_get_data(modem); + DBG("%p", modem); + ofono_netreg_create(modem, 0, "atmodem", data->aux); } diff --git a/ofono/plugins/samsung.c b/ofono/plugins/samsung.c index 68a9b0a6d..1780943be 100644 --- a/ofono/plugins/samsung.c +++ b/ofono/plugins/samsung.c @@ -149,47 +149,21 @@ static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) static int samsung_enable(struct ofono_modem *modem) { struct samsung_data *data = ofono_modem_get_data(modem); - GAtSyntax *syntax; - GIOChannel *channel; - GHashTable *options; - const char *device; - - device = ofono_modem_get_string(modem, "ControlPort"); - if (device == NULL) - return -EINVAL; - - options = g_hash_table_new(g_str_hash, g_str_equal); - if (options == NULL) - return -ENOMEM; - - g_hash_table_insert(options, "Baud", "115200"); - g_hash_table_insert(options, "Parity", "none"); - g_hash_table_insert(options, "StopBits", "1"); - g_hash_table_insert(options, "DataBits", "8"); - g_hash_table_insert(options, "XonXoff", "off"); - g_hash_table_insert(options, "RtsCts", "on"); - g_hash_table_insert(options, "Local", "on"); - g_hash_table_insert(options, "Read", "on"); - - channel = g_at_tty_open(device, options); - - g_hash_table_destroy(options); - - if (channel == NULL) - return -EIO; - - syntax = g_at_syntax_new_gsm_permissive(); - data->chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); + data->chat = at_util_open_device(modem, "ControlPort", + samsung_debug, "Device: ", + "Baud", "115200", + "Parity", "none", + "StopBits", "1", + "DataBits", "8", + "XonXoff", "off", + "RtsCts", "on", + "Local", "on", + "Read", "on", + NULL); if (data->chat == NULL) return -ENOMEM; - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(data->chat, samsung_debug, "Device: "); - g_at_chat_send(data->chat, "ATE0", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+CMEE=1", NULL, NULL, NULL, NULL); diff --git a/ofono/plugins/sierra.c b/ofono/plugins/sierra.c index 458fec2ff..0cc1f3a7d 100644 --- a/ofono/plugins/sierra.c +++ b/ofono/plugins/sierra.c @@ -94,34 +94,7 @@ static void sierra_remove(struct ofono_modem *modem) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GAtSyntax *syntax; - GIOChannel *channel; - GAtChat *chat; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, sierra_debug, debug); - - return chat; + return at_util_open_device(modem, key, sierra_debug, debug, NULL); } static void sim_state_cb(gboolean present, gpointer user_data) @@ -149,6 +122,8 @@ static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) if (!ok) { g_at_chat_unref(data->modem); data->modem = NULL; + ofono_modem_set_powered(modem, FALSE); + return; } data->sim_state_query = at_util_sim_state_query_new(data->modem, diff --git a/ofono/plugins/sim900.c b/ofono/plugins/sim900.c index eeeb248f7..256483ead 100644 --- a/ofono/plugins/sim900.c +++ b/ofono/plugins/sim900.c @@ -115,7 +115,7 @@ static void check_model(gboolean ok, GAtResult *result, gpointer user_data) if (strstr(model, "SIM800")) data->modem_type = SIM800; else if (strstr(model, "SIM900")) - data->modem_type = SIM800; + data->modem_type = SIM900; else data->modem_type = SIMCOM_UNKNOWN; } diff --git a/ofono/plugins/speedup.c b/ofono/plugins/speedup.c index d1ea35a12..0b5d22892 100644 --- a/ofono/plugins/speedup.c +++ b/ofono/plugins/speedup.c @@ -109,34 +109,7 @@ static void speedup_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GIOChannel *channel; - GAtSyntax *syntax; - GAtChat *chat; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, speedup_debug, debug); - - return chat; + return at_util_open_device(modem, key, speedup_debug, debug, NULL); } static void sim_state_cb(gboolean present, gpointer user_data) diff --git a/ofono/plugins/speedupcdma.c b/ofono/plugins/speedupcdma.c index 8e5f32431..779c37bd1 100644 --- a/ofono/plugins/speedupcdma.c +++ b/ofono/plugins/speedupcdma.c @@ -38,6 +38,7 @@ #include #include +#include "drivers/atmodem/atutil.h" #include "drivers/atmodem/vendor.h" struct speedupcdma_data { @@ -102,34 +103,7 @@ static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GIOChannel *channel; - GAtSyntax *syntax; - GAtChat *chat; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, speedupcdma_debug, debug); - - return chat; + return at_util_open_device(modem, key, speedupcdma_debug, debug, NULL); } static int speedupcdma_enable(struct ofono_modem *modem) diff --git a/ofono/plugins/telit.c b/ofono/plugins/telit.c index 1a3ade070..094d762f0 100644 --- a/ofono/plugins/telit.c +++ b/ofono/plugins/telit.c @@ -116,41 +116,9 @@ static void telit_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GAtSyntax *syntax; - GIOChannel *channel; - GAtChat *chat; - GHashTable *options; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - options = g_hash_table_new(g_str_hash, g_str_equal); - if (options == NULL) - return NULL; - - g_hash_table_insert(options, "Baud", "115200"); - channel = g_at_tty_open(device, options); - g_hash_table_destroy(options); - - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, telit_debug, debug); - - return chat; + return at_util_open_device(modem, key, telit_debug, debug, + "Baud", "115200", + NULL); } static void switch_sim_state_status(struct ofono_modem *modem, int status) diff --git a/ofono/plugins/ublox.c b/ofono/plugins/ublox.c index 2b3fc5ebc..efcd40cff 100644 --- a/ofono/plugins/ublox.c +++ b/ofono/plugins/ublox.c @@ -29,6 +29,7 @@ #include #include #include +#include #define OFONO_API_SUBJECT_TO_CHANGE #include @@ -40,25 +41,36 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include +#include -#include #include +#include +static const char *uusbconf_prefix[] = { "+UUSBCONF:", NULL }; static const char *none_prefix[] = { NULL }; -enum supported_models { - SARA_G270 = 1102, - TOBYL2_COMPATIBLE_MODE = 1141, - TOBYL2_MEDIUM_THROUGHPUT_MODE = 1143, - TOBYL2_HIGH_THROUGHPUT_MODE = 1146, +enum ublox_device_flags { + UBLOX_DEVICE_F_HIGH_THROUGHPUT_MODE = (1 << 0), }; struct ublox_data { GAtChat *modem; GAtChat *aux; - int model_id; enum ofono_vendor vendor_family; + + const struct ublox_model *model; + int flags; + + struct l_timeout *init_timeout; + int init_count; + guint init_cmd; }; static void ublox_debug(const char *str, void *user_data) @@ -95,124 +107,244 @@ static void ublox_remove(struct ofono_modem *modem) g_free(data); } -static GAtChat *open_device(struct ofono_modem *modem, - const char *key, char *debug) +static void close_devices(struct ofono_modem* modem) +{ + struct ublox_data * data = ofono_modem_get_data(modem); + + g_at_chat_unref(data->aux); + data->aux = NULL; + g_at_chat_unref(data->modem); + data->modem = NULL; + ofono_modem_set_powered(modem, FALSE); +} + +static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) { - const char *device; - GAtSyntax *syntax; - GIOChannel *channel; - GAtChat *chat; + struct ofono_modem *modem = user_data; - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; + DBG("ok %d", ok); - DBG("%s %s", key, device); + if (!ok) { + close_devices(modem); + return; + } + + ofono_modem_set_powered(modem, TRUE); +} + +static void query_usbconf_cb(gboolean ok, + GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct ublox_data *data = ofono_modem_get_data(modem); + GAtResultIter iter; + int profile; + + if (!ok) { + ofono_error("Unable to query USB configuration"); + goto error; + } - channel = g_at_tty_open(device, NULL); - if (channel == NULL) - return NULL; + g_at_result_iter_init(&iter, result); - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); +retry: + if (!g_at_result_iter_next(&iter, "+UUSBCONF:")) { + ofono_error("Unable to query USB configuration"); + goto error; + } - g_io_channel_unref(channel); + if (!g_at_result_iter_next_number(&iter, &profile)) + goto retry; - if (chat == NULL) - return NULL; + switch (profile) { + case 0: /* Fairly back compatible */ + case 1: /* Fairly back compatible plus audio */ + break; + case 2: /* Low/medium throughput */ + ofono_error("Medium throughput mode not supported"); + goto error; + case 3: /* High throughput mode */ + data->flags |= UBLOX_DEVICE_F_HIGH_THROUGHPUT_MODE; + break; + default: + ofono_error("Unexpected USB profile: %d", profile); + goto error; + } - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, ublox_debug, debug); + if (g_at_chat_send(data->aux, "AT+CFUN=4", none_prefix, + cfun_enable, modem, NULL)) + return; - return chat; +error: + close_devices(modem); } -static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) +static void query_model_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_modem *modem = user_data; - struct ublox_data * data = ofono_modem_get_data(modem); + struct ublox_data *data = ofono_modem_get_data(modem); + struct ofono_error error; + const char *model; + const struct ublox_model *m; - DBG("ok %d", ok); + decode_at_error(&error, g_at_result_final_response(result)); - if (!ok) { - g_at_chat_unref(data->aux); - data->aux = NULL; - g_at_chat_unref(data->modem); - data->modem = NULL; - ofono_modem_set_powered(modem, FALSE); - return; + if (!ok) + goto fail; + + if (at_util_parse_attr(result, "", &model) == FALSE) { + ofono_error("Failed to query modem model"); + goto fail; } - if (data->model_id == TOBYL2_HIGH_THROUGHPUT_MODE) - /* use bridged mode until routed mode support is added */ - g_at_chat_send(data->aux, "AT+UBMCONF=2", none_prefix, - NULL, NULL, NULL); + m = ublox_model_from_name(model); + if (!m) { + ofono_error("Unrecognized model: %s", model); + goto fail; + } - ofono_modem_set_powered(modem, TRUE); + data->model = m; + + DBG("Model: %s", data->model->name); + + data->vendor_family = OFONO_VENDOR_UBLOX; + + if (data->model->flags & UBLOX_F_HAVE_USBCONF) { + if (g_at_chat_send(data->aux, "AT+UUSBCONF?", uusbconf_prefix, + query_usbconf_cb, modem, NULL)) + return; + + ofono_error("Unable to query USB configuration"); + goto fail; + } + + if (g_at_chat_send(data->aux, "AT+CFUN=4", none_prefix, + cfun_enable, modem, NULL)) + return; + +fail: + close_devices(modem); } -static int ublox_enable(struct ofono_modem *modem) +static void init_cmd_cb(gboolean ok, GAtResult *result, void *user_data) { + struct ofono_modem *modem = user_data; struct ublox_data *data = ofono_modem_get_data(modem); - const char *model_str = NULL; DBG("%p", modem); - model_str = ofono_modem_get_string(modem, "Model"); - if (model_str == NULL) - return -EINVAL; + if (!ok) + goto fail; - /* - * Toby L2 devices are more complex and special than previously - * supported U-Blox devices. So they need a vendor of their own. + /* When the 'init command' succeeds, we insert an additional + * delay of 1 second before proceeding with the actual + * intialization of the device. We reuse the init_timeout + * instance for this, just clearing the command to indicate + * that additional retries aren't necessary. */ - data->model_id = atoi(model_str); + data->init_cmd = 0; + data->init_count = 0; + l_timeout_modify_ms(data->init_timeout, 1000); - switch (data->model_id) { - case SARA_G270: - data->vendor_family = OFONO_VENDOR_UBLOX; - break; - case TOBYL2_COMPATIBLE_MODE: - case TOBYL2_HIGH_THROUGHPUT_MODE: - data->vendor_family = OFONO_VENDOR_UBLOX_TOBY_L2; - break; - case TOBYL2_MEDIUM_THROUGHPUT_MODE: - DBG("low/medium throughtput profile unsupported"); - break; - default: - DBG("unknown ublox model id %d", data->model_id); - return -EINVAL; + return; + +fail: + l_timeout_remove(data->init_timeout); + data->init_timeout = NULL; + + close_devices(modem); +} + +static void init_timeout_cb(struct l_timeout *timeout, void *user_data) +{ + struct ofono_modem *modem = user_data; + struct ublox_data *data = ofono_modem_get_data(modem); + + DBG("%p", modem); + + /* As long as init_cmd is set we need to either keep retrying + * or fail everything after excessive retries + */ + if (data->init_cmd && data->init_count++ < 20) { + g_at_chat_retry(data->aux, data->init_cmd); + l_timeout_modify_ms(timeout, 1000); + return; } - data->aux = open_device(modem, "Aux", "Aux: "); - if (data->aux == NULL) - return -EINVAL; + l_timeout_remove(data->init_timeout); + data->init_timeout = NULL; - if (data->vendor_family == OFONO_VENDOR_UBLOX) { - data->modem = open_device(modem, "Modem", "Modem: "); - if (data->modem == NULL) { - g_at_chat_unref(data->aux); - data->aux = NULL; - return -EIO; - } + if (data->init_cmd) { + ofono_error("failed to init modem after 20 attempts"); + goto fail; + } - g_at_chat_set_slave(data->modem, data->aux); + g_at_chat_send(data->aux, "ATE0", none_prefix, + NULL, NULL, NULL); + g_at_chat_send(data->aux, "AT+CMEE=1", none_prefix, + NULL, NULL, NULL); + + if (g_at_chat_send(data->aux, "AT+CGMM", NULL, + query_model_cb, modem, NULL) > 0) + return; + +fail: + close_devices(modem); +} + +static int ublox_enable(struct ofono_modem *modem) +{ + struct ublox_data *data = ofono_modem_get_data(modem); + + DBG("%p", modem); + + data->aux = at_util_open_device(modem, "Aux", + ublox_debug, "Aux: ", NULL); + /* If this is a serial modem then the device may be behind + * the 'Device' attribute instead... + */ + if (data->aux == NULL) { + data->aux = at_util_open_device(modem, "Device", + ublox_debug, "Aux: ", NULL); + if (data->aux == NULL) + return -EINVAL; + } + data->modem = at_util_open_device(modem, "Modem", + ublox_debug, "Modem: ", NULL); + if (data->modem) { + g_at_chat_set_slave(data->modem, data->aux); g_at_chat_send(data->modem, "ATE0 +CMEE=1", none_prefix, NULL, NULL, NULL); - g_at_chat_send(data->modem, "AT&C0", NULL, NULL, NULL, NULL); } - /* The modem can take a while to wake up if just powered on. */ - g_at_chat_set_wakeup_command(data->aux, "AT\r", 1000, 11000); - - g_at_chat_send(data->aux, "ATE0 +CMEE=1", none_prefix, - NULL, NULL, NULL); + /* + * uBlox devices present their USB interfaces well before those + * interfaces are actually ready to use. The specs say to monitor + * the 'greeting text' to detect whether the device is ready to use; + * unfortunately, other than for the TOBY L4, the greeting text is + * not actually specified. + * + * What has been determined experimentally to work is to probe with + * an 'AT' command until it responds and then wait an additional + * second before continuing with device initialization. Even for + * the TOBY L4 where one should wait for the '+AT: READY' URC + * before intialization, this seems to be sufficient; the 'READY' + * indication always arrives within this time. + * + * (It would be more rigorous to actually wait for the 'READY' + * indication, but that would require knowing the device model + * before the device model is actually queried. Do-able via + * USB Product ID, but overkill when the above seems to work + * reliably.) + */ - g_at_chat_send(data->aux, "AT+CFUN=4", none_prefix, - cfun_enable, modem, NULL); + data->init_count = 0; + data->init_cmd = g_at_chat_send(data->aux, "AT", none_prefix, + init_cmd_cb, modem, NULL); + data->init_timeout = l_timeout_create_ms(500, init_timeout_cb, modem, + NULL); return -EINPROGRESS; } @@ -220,15 +352,15 @@ static int ublox_enable(struct ofono_modem *modem) static void cfun_disable(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_modem *modem = user_data; - struct ublox_data *data = ofono_modem_get_data(modem); DBG(""); - g_at_chat_unref(data->aux); - data->aux = NULL; + if (!ok) { + ofono_error("Failed to disable modem"); + return; + } - if (ok) - ofono_modem_set_powered(modem, FALSE); + close_devices(modem); } static int ublox_disable(struct ofono_modem *modem) @@ -237,10 +369,10 @@ static int ublox_disable(struct ofono_modem *modem) DBG("%p", modem); + l_timeout_remove(data->init_timeout); + g_at_chat_cancel_all(data->modem); g_at_chat_unregister_all(data->modem); - g_at_chat_unref(data->modem); - data->modem = NULL; g_at_chat_cancel_all(data->aux); g_at_chat_unregister_all(data->aux); @@ -308,34 +440,51 @@ static void ublox_post_sim(struct ofono_modem *modem) struct ofono_gprs *gprs; struct ofono_gprs_context *gc; GAtChat *chat = data->modem ? data->modem : data->aux; - const char *driver = data->model_id == TOBYL2_HIGH_THROUGHPUT_MODE ? - "ubloxmodem" : "atmodem"; - /* Toby L2: Create same number of contexts as supported PDP contexts. */ - int ncontexts = data->model_id == TOBYL2_HIGH_THROUGHPUT_MODE ? 8 : 1; + struct ofono_message_waiting *mw; + const char *driver; + const char *iface; + int variant; DBG("%p", modem); gprs = ofono_gprs_create(modem, data->vendor_family, "atmodem", data->aux); - while (ncontexts) { - gc = ofono_gprs_context_create(modem, data->vendor_family, - driver, chat); + iface = ofono_modem_get_string(modem, "NetworkInterface"); + if (iface) { + driver = "ubloxmodem"; + variant = ublox_model_to_id(data->model); + } else { + driver = "atmodem"; + variant = OFONO_VENDOR_UBLOX; + } + + gc = ofono_gprs_context_create(modem, variant, driver, chat); + if (gprs && gc) + ofono_gprs_add_context(gprs, gc); - if (gprs && gc) - ofono_gprs_add_context(gprs, gc); + ofono_lte_create(modem, + ublox_model_to_id(data->model), "ubloxmodem", data->aux); - --ncontexts; - } + ofono_sms_create(modem, 0, "atmodem", data->aux); + + ofono_ussd_create(modem, 0, "atmodem", data->aux); + ofono_call_forwarding_create(modem, 0, "atmodem", data->aux); + ofono_call_settings_create(modem, 0, "atmodem", data->aux); + ofono_call_meter_create(modem, 0, "atmodem", data->aux); + ofono_call_barring_create(modem, 0, "atmodem", data->aux); - ofono_lte_create(modem, 0, "ubloxmodem", data->aux); + mw = ofono_message_waiting_create(modem); + if (mw) + ofono_message_waiting_register(mw); } static void ublox_post_online(struct ofono_modem *modem) { struct ublox_data *data = ofono_modem_get_data(modem); - ofono_netreg_create(modem, data->vendor_family, "atmodem", data->aux); + ofono_netreg_create(modem, + ublox_model_to_id(data->model), "ubloxmodem", data->aux); ofono_netmon_create(modem, data->vendor_family, "ubloxmodem", data->aux); } diff --git a/ofono/plugins/udevng.c b/ofono/plugins/udevng.c index 2b0524b61..25593c6a9 100644 --- a/ofono/plugins/udevng.c +++ b/ofono/plugins/udevng.c @@ -41,6 +41,7 @@ enum modem_type { MODEM_TYPE_USB, MODEM_TYPE_SERIAL, + MODEM_TYPE_PCIE, }; struct modem_info { @@ -837,7 +838,7 @@ static gboolean setup_samsung(struct modem_info *modem) return TRUE; } -static gboolean setup_quectel(struct modem_info *modem) +static gboolean setup_quectel_usb(struct modem_info *modem) { const char *aux = NULL, *mdm = NULL; GSList *list; @@ -877,6 +878,42 @@ static gboolean setup_quectel(struct modem_info *modem) return TRUE; } +static gboolean setup_quectel_serial(struct modem_info *modem) +{ + struct serial_device_info *info = modem->serial; + const char *value; + + value = udev_device_get_property_value(info->dev, + "OFONO_QUECTEL_GPIO_CHIP"); + if (value) + ofono_modem_set_string(modem->modem, "GpioChip", value); + + value = udev_device_get_property_value(info->dev, + "OFONO_QUECTEL_GPIO_OFFSET"); + if (value) + ofono_modem_set_string(modem->modem, "GpioOffset", value); + + value = udev_device_get_property_value(info->dev, + "OFONO_QUECTEL_MUX"); + if (value) + ofono_modem_set_string(modem->modem, "Mux", value); + + value = udev_device_get_property_value(info->dev, + "OFONO_QUECTEL_RTSCTS"); + ofono_modem_set_string(modem->modem, "RtsCts", value ? value : "off"); + ofono_modem_set_string(modem->modem, "Device", info->devnode); + + return TRUE; +} + +static gboolean setup_quectel(struct modem_info *modem) +{ + if (modem->serial) + return setup_quectel_serial(modem); + else + return setup_quectel_usb(modem); +} + static gboolean setup_quectelqmi(struct modem_info *modem) { const char *qmi = NULL, *net = NULL, *gps = NULL, *aux = NULL; @@ -1090,11 +1127,17 @@ static gboolean setup_ublox(struct modem_info *modem) * - high throughput profile : 224/1/3 */ } else if (g_strcmp0(info->interface, "2/2/1") == 0) { - if (g_strcmp0(info->number, "02") == 0) - aux = info->devnode; - else if (g_strcmp0(info->number, "00") == 0) + if (!g_strcmp0(modem->model, "1010")) { + if (g_strcmp0(info->number, "06") == 0) + aux = info->devnode; + } else { + if (g_strcmp0(info->number, "02") == 0) + aux = info->devnode; + } + if (g_strcmp0(info->number, "00") == 0) mdm = info->devnode; } else if (g_strcmp0(info->interface, "2/6/0") == 0 || + g_strcmp0(info->interface, "2/13/0") == 0 || g_strcmp0(info->interface, "10/0/0") == 0 || g_strcmp0(info->interface, "224/1/3") == 0) { net = info->devnode; @@ -1111,7 +1154,6 @@ static gboolean setup_ublox(struct modem_info *modem) ofono_modem_set_string(modem->modem, "Aux", aux); ofono_modem_set_string(modem->modem, "Modem", mdm); - ofono_modem_set_string(modem->modem, "Model", modem->model); ofono_modem_set_string(modem->modem, "NetworkInterface", net); return TRUE; @@ -1179,7 +1221,7 @@ static gboolean setup_gemalto(struct modem_info* modem) static gboolean setup_xmm7xxx(struct modem_info *modem) { - const char *mdm = NULL, *net = NULL; + const char *mdm = NULL, *net = NULL, *net2 = NULL, *net3 = NULL; GSList *list; DBG("%s %s %s %s %s %s\n", modem->syspath, modem->devname, @@ -1192,22 +1234,47 @@ static gboolean setup_xmm7xxx(struct modem_info *modem) info->interface, info->number, info->label, info->sysattr, info->subsystem); - if (g_strcmp0(modem->model,"095a") == 0) { - if (g_strcmp0(info->subsystem, "tty") == 0) { - if (g_strcmp0(info->number, "00") == 0) - mdm = info->devnode; - } else if (g_strcmp0(info->subsystem, "net") == 0) { - if (g_strcmp0(info->number, "06") == 0) - net = info->devnode; + if (g_strcmp0(info->subsystem, "pci") == 0) { + if ((g_strcmp0(modem->vendor, "0x8086") == 0) && + (g_strcmp0(modem->model, "0x7560") == 0)) { + mdm = "/dev/iat"; + net = "inm0"; + net2 = "inm1"; + net3 = "inm2"; + ofono_modem_set_string(modem->modem, + "CtrlPath", "/PCIE/IOSM/CTRL/1"); + ofono_modem_set_string(modem->modem, "DataPath", + "/PCIE/IOSM/IPS/"); } - } else { - if (g_strcmp0(info->subsystem, "tty") == 0) { - if (g_strcmp0(info->number, "02") == 0) - mdm = info->devnode; - } else if (g_strcmp0(info->subsystem, "net") == 0) { - if (g_strcmp0(info->number, "00") == 0) - net = info->devnode; + } else { /* For USB */ + if (g_strcmp0(modem->model, "095a") == 0) { + if (g_strcmp0(info->subsystem, "tty") == 0) { + if (g_strcmp0(info->number, "00") == 0) + mdm = info->devnode; + } else if (g_strcmp0(info->subsystem, "net") + == 0) { + if (g_strcmp0(info->number, "06") == 0) + net = info->devnode; + if (g_strcmp0(info->number, "08") == 0) + net2 = info->devnode; + if (g_strcmp0(info->number, "0a") == 0) + net3 = info->devnode; + } + } else { + if (g_strcmp0(info->subsystem, "tty") == 0) { + if (g_strcmp0(info->number, "02") == 0) + mdm = info->devnode; + } else if (g_strcmp0(info->subsystem, "net") + == 0) { + if (g_strcmp0(info->number, "00") == 0) + net = info->devnode; + } } + + ofono_modem_set_string(modem->modem, "CtrlPath", + "/USBCDC/0"); + ofono_modem_set_string(modem->modem, "DataPath", + "/USBHS/NCM/"); } } @@ -1219,12 +1286,20 @@ static gboolean setup_xmm7xxx(struct modem_info *modem) ofono_modem_set_string(modem->modem, "Modem", mdm); ofono_modem_set_string(modem->modem, "NetworkInterface", net); + if (net2) + ofono_modem_set_string(modem->modem, "NetworkInterface2", net2); + + if (net3) + ofono_modem_set_string(modem->modem, "NetworkInterface3", net3); + return TRUE; } -static gboolean setup_sim7100(struct modem_info *modem) +static gboolean setup_sim7x00(struct modem_info *modem) { - const char *at = NULL, *ppp = NULL, *gps = NULL, *diag = NULL, *audio = NULL; + const char *audio = NULL, *diag = NULL, *gps = NULL; + const char *mdm = NULL, *net = NULL, *ppp = NULL; + const char *qmi = NULL; GSList *list; DBG("%s", modem->syspath); @@ -1232,10 +1307,12 @@ static gboolean setup_sim7100(struct modem_info *modem) for (list = modem->devices; list; list = list->next) { struct device_info *info = list->data; - DBG("%s %s", info->devnode, info->number); + DBG("%s %s %s %s %s %s", info->devnode, info->interface, + info->number, info->label, + info->sysattr, info->subsystem); /* - * Serial port layout: + * SIM7100 serial port layout: * 0: QCDM/DIAG * 1: NMEA * 2: AT @@ -1244,29 +1321,52 @@ static gboolean setup_sim7100(struct modem_info *modem) * * -- https://www.spinics.net/lists/linux-usb/msg135728.html */ - if (g_strcmp0(info->number, "00") == 0) - diag = info->devnode; - else if (g_strcmp0(info->number, "01") == 0) - gps = info->devnode; - else if (g_strcmp0(info->number, "02") == 0) - at = info->devnode; - else if (g_strcmp0(info->number, "03") == 0) - ppp = info->devnode; - else if (g_strcmp0(info->number, "04") == 0) - audio = info->devnode; + if (g_strcmp0(info->subsystem, "usbmisc") == 0) /* cdc-wdm */ + qmi = info->devnode; /* SIM7600 */ + else if (g_strcmp0(info->subsystem, "net") == 0) /* wwan */ + net = info->devnode; /* SIM7600 */ + else if (g_strcmp0(info->subsystem, "tty") == 0) { + if (g_strcmp0(info->interface, "255/255/255") == 0) { + if (g_strcmp0(info->number, "00") == 0) + diag = info->devnode; /* SIM7x00 */ + } else if (g_strcmp0(info->interface, "255/0/0") == 0) { + if (g_strcmp0(info->number, "01") == 0) + gps = info->devnode; /* SIM7x00 */ + else if (g_strcmp0(info->number, "02") == 0) + mdm = info->devnode; /* SIM7x00 */ + else if (g_strcmp0(info->number, "03") == 0) + ppp = info->devnode; /* SIM7100 */ + else if (g_strcmp0(info->number, "04") == 0) + audio = info->devnode; /* SIM7100 */ + } + } } - if (at == NULL) + if (mdm == NULL) return FALSE; - DBG("at=%s ppp=%s gps=%s diag=%s, audio=%s", at, ppp, gps, diag, audio); + if (qmi != NULL && net != NULL) { + DBG("qmi=%s net=%s mdm=%s gps=%s diag=%s", + qmi, net, mdm, gps, diag); + + ofono_modem_set_driver(modem->modem, "gobi"); + + ofono_modem_set_string(modem->modem, "Device", qmi); + ofono_modem_set_string(modem->modem, "Modem", mdm); + ofono_modem_set_string(modem->modem, "NetworkInterface", net); + } else { + DBG("at=%s ppp=%s gps=%s diag=%s, audio=%s", + mdm, ppp, gps, diag, audio); + + ofono_modem_set_driver(modem->modem, "sim7100"); + + ofono_modem_set_string(modem->modem, "AT", mdm); + ofono_modem_set_string(modem->modem, "PPP", ppp); + ofono_modem_set_string(modem->modem, "Audio", audio); + } - ofono_modem_set_string(modem->modem, "AT", at); - ofono_modem_set_string(modem->modem, "PPP", ppp); ofono_modem_set_string(modem->modem, "GPS", gps); ofono_modem_set_string(modem->modem, "Diag", diag); - ofono_modem_set_string(modem->modem, "Audio", audio); - return TRUE; } @@ -1290,7 +1390,7 @@ static struct { { "telit", setup_telit, "device/interface" }, { "telitqmi", setup_telitqmi }, { "simcom", setup_simcom }, - { "sim7100", setup_sim7100 }, + { "sim7x00", setup_sim7x00 }, { "zte", setup_zte }, { "icera", setup_icera }, { "samsung", setup_samsung }, @@ -1360,6 +1460,7 @@ static void destroy_modem(gpointer data) switch (modem->type) { case MODEM_TYPE_USB: + case MODEM_TYPE_PCIE: for (list = modem->devices; list; list = list->next) { struct device_info *info = list->data; @@ -1390,6 +1491,7 @@ static gboolean check_remove(gpointer key, gpointer value, gpointer user_data) switch (modem->type) { case MODEM_TYPE_USB: + case MODEM_TYPE_PCIE: for (list = modem->devices; list; list = list->next) { struct device_info *info = list->data; @@ -1522,7 +1624,8 @@ static void add_serial_device(struct udev_device *dev) static void add_device(const char *syspath, const char *devname, const char *driver, const char *vendor, - const char *model, struct udev_device *device) + const char *model, struct udev_device *device, + enum modem_type type) { struct udev_device *usb_interface; const char *devpath, *devnode, *interface, *number; @@ -1535,25 +1638,13 @@ static void add_device(const char *syspath, const char *devname, if (devpath == NULL) return; - devnode = udev_device_get_devnode(device); - if (devnode == NULL) { - devnode = udev_device_get_property_value(device, "INTERFACE"); - if (devnode == NULL) - return; - } - - usb_interface = udev_device_get_parent_with_subsystem_devtype(device, - "usb", "usb_interface"); - if (usb_interface == NULL) - return; - modem = g_hash_table_lookup(modem_list, syspath); if (modem == NULL) { modem = g_try_new0(struct modem_info, 1); if (modem == NULL) return; - modem->type = MODEM_TYPE_USB; + modem->type = type; modem->syspath = g_strdup(syspath); modem->devname = g_strdup(devname); modem->driver = g_strdup(driver); @@ -1565,8 +1656,37 @@ static void add_device(const char *syspath, const char *devname, g_hash_table_replace(modem_list, modem->syspath, modem); } - interface = udev_device_get_property_value(usb_interface, "INTERFACE"); - number = udev_device_get_property_value(device, "ID_USB_INTERFACE_NUM"); + if (modem->type == MODEM_TYPE_USB) { + devnode = udev_device_get_devnode(device); + if (devnode == NULL) { + devnode = udev_device_get_property_value(device, + "INTERFACE"); + if (devnode == NULL) + return; + } + + usb_interface = udev_device_get_parent_with_subsystem_devtype( + device, "usb", + "usb_interface"); + if (usb_interface == NULL) + return; + + interface = udev_device_get_property_value(usb_interface, + "INTERFACE"); + number = udev_device_get_property_value(device, + "ID_USB_INTERFACE_NUM"); + + label = udev_device_get_property_value(device, "OFONO_LABEL"); + if (!label) + label = udev_device_get_property_value(usb_interface, + "OFONO_LABEL"); + } else { + devnode = NULL; + interface = udev_device_get_property_value(device, + "INTERFACE"); + number = NULL; + label = NULL; + } /* If environment variable is not set, get value from attributes (or parent's ones) */ if (number == NULL) { @@ -1580,11 +1700,6 @@ static void add_device(const char *syspath, const char *devname, } } - label = udev_device_get_property_value(device, "OFONO_LABEL"); - if (!label) - label = udev_device_get_property_value(usb_interface, - "OFONO_LABEL"); - subsystem = udev_device_get_subsystem(device); if (modem->sysattr != NULL) @@ -1663,7 +1778,8 @@ static struct { { "novatel", "option", "1410" }, { "zte", "option", "19d2" }, { "simcom", "option", "05c6", "9000" }, - { "sim7100", "option", "1e0e", "9001" }, + { "sim7x00", "option", "1e0e", "9001" }, + { "sim7x00", "qmi_wwan", "1e0e", "9001" }, { "telit", "usbserial", "1bc7" }, { "telit", "option", "1bc7" }, { "telit", "cdc_acm", "1bc7", "0021" }, @@ -1678,7 +1794,11 @@ static struct { { "quectelqmi", "qcserial", "2c7c", "0121" }, { "quectelqmi", "qmi_wwan", "2c7c", "0125" }, { "quectelqmi", "qcserial", "2c7c", "0125" }, + { "ublox", "cdc_acm", "1546", "1010" }, + { "ublox", "cdc_ncm", "1546", "1010" }, { "ublox", "cdc_acm", "1546", "1102" }, + { "ublox", "cdc_acm", "1546", "110a" }, + { "ublox", "cdc_ncm", "1546", "110a" }, { "ublox", "rndis_host", "1546", "1146" }, { "ublox", "cdc_acm", "1546", "1146" }, { "gemalto", "option", "1e2d", "0053" }, @@ -1773,9 +1893,64 @@ static void check_usb_device(struct udev_device *device) return; } - add_device(syspath, devname, driver, vendor, model, device); + add_device(syspath, devname, driver, vendor, model, device, + MODEM_TYPE_USB); } +static const struct { + const char *driver; + const char *drv; + const char *vid; + const char *pid; +} pci_driver_list[] = { + { "xmm7xxx", "imc_ipc", "0x8086", "0x7560"}, + { } +}; + +static void check_pci_device(struct udev_device *device) +{ + const char *syspath, *devname, *driver; + const char *vendor = NULL, *model = NULL, *drv = NULL; + unsigned int i; + + syspath = udev_device_get_syspath(device); + + if (syspath == NULL) + return; + + devname = udev_device_get_devnode(device); + vendor = udev_device_get_sysattr_value(device, "vendor"); + model = udev_device_get_sysattr_value(device, "device"); + driver = udev_device_get_property_value(device, "OFONO_DRIVER"); + drv = udev_device_get_property_value(device, "DRIVER"); + DBG("%s [%s:%s]", drv, vendor, model); + + if (vendor == NULL || model == NULL || drv == NULL) + return; + + for (i = 0; pci_driver_list[i].driver; i++) { + if (g_str_equal(pci_driver_list[i].drv, drv) == FALSE) + continue; + + if (pci_driver_list[i].vid) { + if (!g_str_equal(pci_driver_list[i].vid, vendor)) + continue; + } + + if (pci_driver_list[i].pid) { + if (!g_str_equal(pci_driver_list[i].pid, model)) + continue; + } + + driver = pci_driver_list[i].driver; + } + + if (driver == NULL) + return; + + add_device(syspath, devname, driver, vendor, model, device, + MODEM_TYPE_PCIE); +} static void check_device(struct udev_device *device) { const char *bus; @@ -1790,6 +1965,8 @@ static void check_device(struct udev_device *device) if ((g_str_equal(bus, "usb") == TRUE) || (g_str_equal(bus, "usbmisc") == TRUE)) check_usb_device(device); + else if (g_str_equal(bus, "pci") == TRUE) + check_pci_device(device); else add_serial_device(device); @@ -1850,6 +2027,7 @@ static void enumerate_devices(struct udev *context) udev_enumerate_add_match_subsystem(enumerate, "usbmisc"); udev_enumerate_add_match_subsystem(enumerate, "net"); udev_enumerate_add_match_subsystem(enumerate, "hsi"); + udev_enumerate_add_match_subsystem(enumerate, "pci"); udev_enumerate_scan_devices(enumerate); diff --git a/ofono/plugins/wavecom.c b/ofono/plugins/wavecom.c index 7f24eae91..03830bf51 100644 --- a/ofono/plugins/wavecom.c +++ b/ofono/plugins/wavecom.c @@ -48,6 +48,7 @@ #include #include +#include #include @@ -70,52 +71,19 @@ static void wavecom_debug(const char *str, void *user_data) static int wavecom_enable(struct ofono_modem *modem) { GAtChat *chat; - GIOChannel *channel; - GAtSyntax *syntax; - const char *device; - GHashTable *options; DBG("%p", modem); - device = ofono_modem_get_string(modem, "Device"); - if (device == NULL) + chat = at_util_open_device(modem, "Device", wavecom_debug, "", + "Baud", "115200", + "Parity", "none", + "StopBits", "1", + "DataBits", "8", + NULL); + if (!chat) return -EINVAL; - options = g_hash_table_new(g_str_hash, g_str_equal); - - if (options == NULL) - return -ENOMEM; - - g_hash_table_insert(options, "Baud", "115200"); - g_hash_table_insert(options, "Parity", "none"); - g_hash_table_insert(options, "StopBits", "1"); - g_hash_table_insert(options, "DataBits", "8"); - - channel = g_at_tty_open(device, options); - - g_hash_table_destroy(options); - - if (channel == NULL) - return -EIO; - - /* - * Could not figure out whether it is fully compliant or not, use - * permissive for now - * */ - syntax = g_at_syntax_new_gsm_permissive(); - - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - g_io_channel_unref(channel); - - if (chat == NULL) - return -ENOMEM; - g_at_chat_add_terminator(chat, "+CPIN:", 6, TRUE); - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, wavecom_debug, ""); - ofono_modem_set_data(modem, chat); return 0; diff --git a/ofono/plugins/xmm7xxx.c b/ofono/plugins/xmm7xxx.c index 237c62cc2..a544798aa 100644 --- a/ofono/plugins/xmm7xxx.c +++ b/ofono/plugins/xmm7xxx.c @@ -965,41 +965,9 @@ static void xmm7xxx_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GAtSyntax *syntax; - GIOChannel *channel; - GAtChat *chat; - GHashTable *options; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - options = g_hash_table_new(g_str_hash, g_str_equal); - if (options == NULL) - return NULL; - - g_hash_table_insert(options, "Baud", "115200"); - channel = g_at_tty_open(device, options); - g_hash_table_destroy(options); - - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, xmm7xxx_debug, debug); - - return chat; + return at_util_open_device(modem, key, xmm7xxx_debug, debug, + "Baud", "115200", + NULL); } static void switch_sim_state_status(struct ofono_modem *modem, int status) @@ -1223,7 +1191,7 @@ static void xmm7xxx_pre_sim(struct ofono_modem *modem) DBG("%p", modem); ofono_devinfo_create(modem, OFONO_VENDOR_IFX, "atmodem", data->chat); - data->sim = ofono_sim_create(modem, OFONO_VENDOR_IFX, "atmodem", + data->sim = ofono_sim_create(modem, OFONO_VENDOR_XMM, "atmodem", data->chat); } @@ -1269,6 +1237,7 @@ static void xmm7xxx_post_online(struct ofono_modem *modem) struct xmm7xxx_data *data = ofono_modem_get_data(modem); struct ofono_gprs *gprs; struct ofono_gprs_context *gc; + const char *interface = NULL; DBG("%p", modem); @@ -1276,11 +1245,39 @@ static void xmm7xxx_post_online(struct ofono_modem *modem) gprs = ofono_gprs_create(modem, OFONO_VENDOR_IFX, "atmodem", data->chat); + + interface = ofono_modem_get_string(modem, "NetworkInterface"); gc = ofono_gprs_context_create(modem, OFONO_VENDOR_XMM, "ifxmodem", data->chat); - if (gprs && gc) + if (gprs && gc) { ofono_gprs_add_context(gprs, gc); + ofono_gprs_context_set_interface(gc, interface); + } + + interface = ofono_modem_get_string(modem, "NetworkInterface2"); + + if (interface) { + gc = ofono_gprs_context_create(modem, OFONO_VENDOR_XMM, + "ifxmodem", data->chat); + + if (gprs && gc) { + ofono_gprs_add_context(gprs, gc); + ofono_gprs_context_set_interface(gc, interface); + } + } + + interface = ofono_modem_get_string(modem, "NetworkInterface3"); + + if (interface) { + gc = ofono_gprs_context_create(modem, OFONO_VENDOR_XMM, + "ifxmodem", data->chat); + + if (gprs && gc) { + ofono_gprs_add_context(gprs, gc); + ofono_gprs_context_set_interface(gc, interface); + } + } ofono_ims_create(modem, "xmm7modem", data->chat); ofono_netmon_create(modem, 0, "xmm7modem", data->chat); diff --git a/ofono/plugins/zte.c b/ofono/plugins/zte.c index 53beefe08..b6093af97 100644 --- a/ofono/plugins/zte.c +++ b/ofono/plugins/zte.c @@ -99,51 +99,16 @@ static void zte_debug(const char *str, void *user_data) static GAtChat *open_device(struct ofono_modem *modem, const char *key, char *debug) { - const char *device; - GIOChannel *channel; - GAtSyntax *syntax; - GAtChat *chat; - GHashTable *options; - - device = ofono_modem_get_string(modem, key); - if (device == NULL) - return NULL; - - DBG("%s %s", key, device); - - options = g_hash_table_new(g_str_hash, g_str_equal); - if (options == NULL) - return NULL; - - g_hash_table_insert(options, "Baud", "115200"); - g_hash_table_insert(options, "Parity", "none"); - g_hash_table_insert(options, "StopBits", "1"); - g_hash_table_insert(options, "DataBits", "8"); - g_hash_table_insert(options, "XonXoff", "off"); - g_hash_table_insert(options, "RtsCts", "on"); - g_hash_table_insert(options, "Local", "on"); - g_hash_table_insert(options, "Read", "on"); - - channel = g_at_tty_open(device, options); - - g_hash_table_destroy(options); - - if (channel == NULL) - return NULL; - - syntax = g_at_syntax_new_gsm_permissive(); - chat = g_at_chat_new(channel, syntax); - g_at_syntax_unref(syntax); - - g_io_channel_unref(channel); - - if (chat == NULL) - return NULL; - - if (getenv("OFONO_AT_DEBUG")) - g_at_chat_set_debug(chat, zte_debug, debug); - - return chat; + return at_util_open_device(modem, key, zte_debug, debug, + "Baud", "115200", + "Parity", "none", + "StopBits", "1", + "DataBits", "8", + "XonXoff", "off", + "RtsCts", "on", + "Local", "on", + "Read", "on", + NULL); } static void zoprt_enable(gboolean ok, GAtResult *result, gpointer user_data) diff --git a/ofono/src/common.c b/ofono/src/common.c index a92d6d24c..68721d8b4 100644 --- a/ofono/src/common.c +++ b/ofono/src/common.c @@ -706,8 +706,14 @@ const char *registration_tech_to_string(enum ofono_access_technology tech) return "hspa"; case ACCESS_TECHNOLOGY_EUTRAN: return "lte"; + case ACCESS_TECHNOLOGY_NB_IOT_M1: + return "lte-cat-m1"; + case ACCESS_TECHNOLOGY_NB_IOT_NB1: + return "lte-cat-nb1"; case OFONO_ACCESS_TECHNOLOGY_NONE: break; + default: + return ""; } return ""; } diff --git a/ofono/src/common.h b/ofono/src/common.h index 0e6ebbd03..08673e6f0 100644 --- a/ofono/src/common.h +++ b/ofono/src/common.h @@ -42,6 +42,10 @@ OFONO_ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA /* 6 */ #define ACCESS_TECHNOLOGY_EUTRAN \ OFONO_ACCESS_TECHNOLOGY_EUTRAN /* 7 */ +#define ACCESS_TECHNOLOGY_NB_IOT_M1 \ + OFONO_ACCESS_TECHNOLOGY_NB_IOT_M1 /* 8 */ +#define ACCESS_TECHNOLOGY_NB_IOT_NB1 \ + OFONO_ACCESS_TECHNOLOGY_NB_IOT_NB1 /* 9 */ /* 27.007 Section 7.2 */ #define NETWORK_REGISTRATION_STATUS_NOT_REGISTERED \ diff --git a/ofono/src/gprs.c b/ofono/src/gprs.c index 4ef44e7c9..e7e9c1ed2 100644 --- a/ofono/src/gprs.c +++ b/ofono/src/gprs.c @@ -110,7 +110,6 @@ struct ipv6_settings { }; struct context_settings { - char *interface; struct ipv4_settings *ipv4; struct ipv6_settings *ipv6; }; @@ -121,6 +120,7 @@ struct ofono_gprs_context { ofono_bool_t inuse; const struct ofono_gprs_context_driver *driver; void *driver_data; + char *interface; struct context_settings *settings; struct ofono_atom *atom; }; @@ -305,6 +305,8 @@ static gboolean assign_context(struct pri_context *ctx, int use_cid) return TRUE; } + ctx->context.cid = 0; + return FALSE; } @@ -361,12 +363,10 @@ static void context_settings_free(struct context_settings *settings) g_free(settings->ipv6); settings->ipv6 = NULL; } - - g_free(settings->interface); - settings->interface = NULL; } static void context_settings_append_ipv4(struct context_settings *settings, + const char *interface, DBusMessageIter *iter) { DBusMessageIter variant; @@ -391,7 +391,7 @@ static void context_settings_append_ipv4(struct context_settings *settings, goto done; ofono_dbus_dict_append(&array, "Interface", - DBUS_TYPE_STRING, &settings->interface); + DBUS_TYPE_STRING, &interface); if (settings->ipv4->proxy) ofono_dbus_dict_append(&array, "Proxy", DBUS_TYPE_STRING, @@ -433,6 +433,7 @@ static void context_settings_append_ipv4(struct context_settings *settings, } static void context_settings_append_ipv4_dict(struct context_settings *settings, + const char *interface, DBusMessageIter *dict) { DBusMessageIter entry; @@ -443,12 +444,13 @@ static void context_settings_append_ipv4_dict(struct context_settings *settings, dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); - context_settings_append_ipv4(settings, &entry); + context_settings_append_ipv4(settings, interface, &entry); dbus_message_iter_close_container(dict, &entry); } static void context_settings_append_ipv6(struct context_settings *settings, + const char *interface, DBusMessageIter *iter) { DBusMessageIter variant; @@ -472,7 +474,7 @@ static void context_settings_append_ipv6(struct context_settings *settings, goto done; ofono_dbus_dict_append(&array, "Interface", - DBUS_TYPE_STRING, &settings->interface); + DBUS_TYPE_STRING, &interface); if (settings->ipv6->ip) ofono_dbus_dict_append(&array, "Address", DBUS_TYPE_STRING, @@ -503,6 +505,7 @@ static void context_settings_append_ipv6(struct context_settings *settings, } static void context_settings_append_ipv6_dict(struct context_settings *settings, + const char *interface, DBusMessageIter *dict) { DBusMessageIter entry; @@ -513,13 +516,14 @@ static void context_settings_append_ipv6_dict(struct context_settings *settings, dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); - context_settings_append_ipv6(settings, &entry); + context_settings_append_ipv6(settings, interface, &entry); dbus_message_iter_close_container(dict, &entry); } static void signal_settings(struct pri_context *ctx, const char *prop, - void (*append)(struct context_settings *, DBusMessageIter *)) + void (*append)(struct context_settings *, + const char *, DBusMessageIter *)) { DBusConnection *conn = ofono_dbus_get_connection(); @@ -527,6 +531,7 @@ static void signal_settings(struct pri_context *ctx, const char *prop, DBusMessage *signal; DBusMessageIter iter; struct context_settings *settings; + const char *interface; signal = dbus_message_new_signal(path, OFONO_CONNECTION_CONTEXT_INTERFACE, @@ -538,12 +543,15 @@ static void signal_settings(struct pri_context *ctx, const char *prop, dbus_message_iter_init_append(signal, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &prop); - if (ctx->context_driver) + if (ctx->context_driver) { settings = ctx->context_driver->settings; - else + interface = ctx->context_driver->interface; + } else { settings = NULL; + interface = NULL; + } - append(settings, &iter); + append(settings, interface, &iter); g_dbus_send_message(conn, signal); } @@ -781,18 +789,16 @@ static void pri_setproxy(const char *interface, const char *proxy) static void pri_reset_context_settings(struct pri_context *ctx) { struct context_settings *settings; - char *interface; + const char *interface; gboolean signal_ipv4; gboolean signal_ipv6; if (ctx->context_driver == NULL) return; + interface = ctx->context_driver->interface; settings = ctx->context_driver->settings; - interface = settings->interface; - settings->interface = NULL; - signal_ipv4 = settings->ipv4 != NULL; signal_ipv6 = settings->ipv6 != NULL; @@ -809,8 +815,6 @@ static void pri_reset_context_settings(struct pri_context *ctx) } pri_ifupdown(interface, FALSE); - - g_free(interface); } static void pri_update_mms_context_settings(struct pri_context *ctx) @@ -818,18 +822,17 @@ static void pri_update_mms_context_settings(struct pri_context *ctx) struct ofono_gprs_context *gc = ctx->context_driver; struct context_settings *settings = gc->settings; - if (ctx->message_proxy) - settings->ipv4->proxy = g_strdup(ctx->message_proxy); + settings->ipv4->proxy = g_strdup(ctx->message_proxy); if (!pri_parse_proxy(ctx, ctx->message_proxy)) pri_parse_proxy(ctx, ctx->message_center); DBG("proxy %s port %u", ctx->proxy_host, ctx->proxy_port); - pri_set_ipv4_addr(settings->interface, settings->ipv4->ip); + pri_set_ipv4_addr(gc->interface, settings->ipv4->ip); if (ctx->proxy_host) - pri_setproxy(settings->interface, ctx->proxy_host); + pri_setproxy(gc->interface, ctx->proxy_host); } static gboolean pri_str_changed(const char *val, const char *newval) @@ -1056,6 +1059,7 @@ static void append_context_properties(struct pri_context *ctx, dbus_bool_t value; const char *strvalue; struct context_settings *settings; + const char *interface; ofono_dbus_dict_append(dict, "Name", DBUS_TYPE_STRING, &name); @@ -1092,13 +1096,16 @@ static void append_context_properties(struct pri_context *ctx, DBUS_TYPE_STRING, &strvalue); } - if (ctx->context_driver) + if (ctx->context_driver) { settings = ctx->context_driver->settings; - else + interface = ctx->context_driver->interface; + } else { settings = NULL; + interface = NULL; + } - context_settings_append_ipv4_dict(settings, dict); - context_settings_append_ipv6_dict(settings, dict); + context_settings_append_ipv4_dict(settings, interface, dict); + context_settings_append_ipv6_dict(settings, interface, dict); } static DBusMessage *pri_get_properties(DBusConnection *conn, @@ -1147,8 +1154,8 @@ static void pri_activate_callback(const struct ofono_error *error, void *data) __ofono_dbus_pending_reply(&ctx->pending, dbus_message_new_method_return(ctx->pending)); - if (gc->settings->interface != NULL) { - pri_ifupdown(gc->settings->interface, TRUE); + if (gc->interface != NULL) { + pri_ifupdown(gc->interface, TRUE); if (ctx->type == OFONO_GPRS_CONTEXT_TYPE_MMS && gc->settings->ipv4) @@ -1225,7 +1232,6 @@ static void pri_read_settings_callback(const struct ofono_error *error, { struct pri_context *pri_ctx = data; struct ofono_gprs_context *gc = pri_ctx->context_driver; - struct ofono_gprs *gprs = pri_ctx->gprs; DBusConnection *conn = ofono_dbus_get_connection(); dbus_bool_t value; @@ -1241,8 +1247,8 @@ static void pri_read_settings_callback(const struct ofono_error *error, pri_ctx->active = TRUE; - if (gc->settings->interface != NULL) { - pri_ifupdown(gc->settings->interface, TRUE); + if (gc->interface != NULL) { + pri_ifupdown(gc->interface, TRUE); pri_context_signal_settings(pri_ctx, gc->settings->ipv4 != NULL, gc->settings->ipv6 != NULL); @@ -1250,19 +1256,11 @@ static void pri_read_settings_callback(const struct ofono_error *error, value = pri_ctx->active; - gprs->flags &= ~GPRS_FLAG_ATTACHING; - - gprs->driver_attached = TRUE; - gprs_set_attached_property(gprs, TRUE); + gprs_set_attached_property(pri_ctx->gprs, TRUE); ofono_dbus_signal_property_changed(conn, pri_ctx->path, OFONO_CONNECTION_CONTEXT_INTERFACE, "Active", DBUS_TYPE_BOOLEAN, &value); - - if (gprs->flags & GPRS_FLAG_RECHECK) { - gprs->flags &= ~GPRS_FLAG_RECHECK; - gprs_netreg_update(gprs); - } } static DBusMessage *pri_set_apn(struct pri_context *ctx, DBusConnection *conn, @@ -1453,7 +1451,7 @@ static DBusMessage *pri_set_message_proxy(struct pri_context *ctx, if (strlen(proxy) > MAX_MESSAGE_PROXY_LENGTH) return __ofono_error_invalid_format(msg); - if (ctx->message_proxy && g_str_equal(ctx->message_proxy, proxy)) + if (g_str_equal(ctx->message_proxy, proxy)) return dbus_message_new_method_return(msg); strcpy(ctx->message_proxy, proxy); @@ -1482,7 +1480,7 @@ static DBusMessage *pri_set_message_center(struct pri_context *ctx, if (strlen(center) > MAX_MESSAGE_CENTER_LENGTH) return __ofono_error_invalid_format(msg); - if (ctx->message_center && g_str_equal(ctx->message_center, center)) + if (g_str_equal(ctx->message_center, center)) return dbus_message_new_method_return(msg); strcpy(ctx->message_center, center); @@ -1810,7 +1808,7 @@ static gboolean context_dbus_unregister(struct pri_context *ctx) if (ctx->active == TRUE) { const char *interface = - ctx->context_driver->settings->interface; + ctx->context_driver->interface; if (ctx->type == OFONO_GPRS_CONTEXT_TYPE_MMS) pri_set_ipv4_addr(interface, NULL); @@ -1900,6 +1898,27 @@ static gboolean have_active_contexts(struct ofono_gprs *gprs) return FALSE; } +static gboolean have_detachable_active_contexts(struct ofono_gprs *gprs) +{ + GSList *l; + + for (l = gprs->contexts; l; l = l->next) { + struct pri_context *ctx; + struct ofono_gprs_context *gc; + + ctx = l->data; + gc = ctx->context_driver; + + if (!gc || !gc->driver->detach_shutdown) + continue; + + if (ctx->active == TRUE) + return TRUE; + } + + return FALSE; +} + #ifdef SAILFISH_OS static bool have_read_settings(struct ofono_gprs *gprs) { @@ -1929,7 +1948,7 @@ static void pri_context_signal_active(struct pri_context *ctx) "Active", DBUS_TYPE_BOOLEAN, &value); } -static void release_active_contexts(struct ofono_gprs *gprs) +static void detach_active_contexts(struct ofono_gprs *gprs) { GSList *l; struct pri_context *ctx; @@ -1958,6 +1977,15 @@ static void release_active_contexts(struct ofono_gprs *gprs) } } +static gboolean on_lte(struct ofono_gprs *gprs) +{ + if (ofono_netreg_get_technology(gprs->netreg) == + ACCESS_TECHNOLOGY_EUTRAN && have_read_settings(gprs)) + return TRUE; + + return FALSE; +} + static void gprs_set_attached(struct ofono_gprs *gprs, ofono_bool_t attached) { if (attached == gprs->attached) @@ -1968,21 +1996,27 @@ static void gprs_set_attached(struct ofono_gprs *gprs, ofono_bool_t attached) * at driver level. "Attached" = TRUE property can't be signalled to * the applications registered on GPRS properties. * Active contexts have to be release at driver level. + * + * Skip that for LTE since the condition to be attached on LTE + * is that a context gets activated */ - if (attached == FALSE) { - release_active_contexts(gprs); - gprs->bearer = -1; - } else if (have_active_contexts(gprs) == TRUE) { - /* - * Some times the context activates after a detach event and - * right before an attach. We close it to avoid unexpected open - * contexts. - */ - release_active_contexts(gprs); - gprs->flags |= GPRS_FLAG_ATTACHED_UPDATE; - return; + if (have_detachable_active_contexts(gprs) && !on_lte(gprs)) { + detach_active_contexts(gprs); + + if (attached == TRUE) { + /* + * Some times the context activates after a detach event + * and right before an attach. We close it to avoid + * unexpected open contexts. + */ + gprs->flags |= GPRS_FLAG_ATTACHED_UPDATE; + return; + } } + if (attached == FALSE) + gprs->bearer = -1; + gprs_set_attached_property(gprs, attached); } @@ -2071,6 +2105,15 @@ static void gprs_netreg_update(struct ofono_gprs *gprs) { ofono_bool_t attach; + /* + * This function can get called by other reasons than netreg + * updating its status. So check if we have a valid netreg status yet. + * The only reason for not having a valid status is basically during + * startup while the netreg atom is fetching the status. + */ + if (gprs->netreg_status < 0) + return; + attach = gprs->netreg_status == NETWORK_REGISTRATION_STATUS_REGISTERED; attach = attach || (gprs->roaming_allowed && @@ -2086,14 +2129,19 @@ static void gprs_netreg_update(struct ofono_gprs *gprs) * context activation if Attached stays FALSE. */ #ifdef SAILFISH_OS - if (ofono_netreg_get_technology(gprs->netreg) == - ACCESS_TECHNOLOGY_EUTRAN && have_read_settings(gprs)) + if (on_lte(gprs)) { /* * For LTE we set attached status only on successful * context activation. + * + * The context could potentially be registered before the + * netreg update is received. */ return; #endif + gprs_attached_update(gprs); + return; + } if (gprs->driver_attached == attach) return; @@ -2115,7 +2163,7 @@ static void netreg_status_changed(int status, int lac, int ci, int tech, { struct ofono_gprs *gprs = data; - DBG("%d", status); + DBG("%d (%s)", status, registration_status_to_string(status)); gprs->netreg_status = status; @@ -2218,7 +2266,7 @@ static DBusMessage *gprs_set_property(DBusConnection *conn, gprs->roaming_allowed = value; if (gprs->settings) { - g_key_file_set_integer(gprs->settings, SETTINGS_GROUP, + g_key_file_set_boolean(gprs->settings, SETTINGS_GROUP, "RoamingAllowed", gprs->roaming_allowed); storage_sync(gprs->imsi, SETTINGS_STORE, @@ -2315,7 +2363,7 @@ static struct pri_context *find_usable_context(struct ofono_gprs *gprs, for (l = gprs->contexts; l; l = l->next) { pri_ctx = l->data; - if (pri_ctx->context.apn == NULL) + if (pri_ctx->context.apn[0] == '\0') return pri_ctx; } @@ -2371,7 +2419,12 @@ void ofono_gprs_cid_activated(struct ofono_gprs *gprs, unsigned int cid, struct pri_context *pri_ctx; struct ofono_gprs_context *gc; - DBG(""); + DBG("cid %u", cid); + + if (!__ofono_atom_get_registered(gprs->atom)) { + DBG("cid %u activated before atom registered", cid); + return; + } if (gprs_cid_taken(gprs, cid)) { DBG("cid %u already activated", cid); @@ -2427,14 +2480,6 @@ void ofono_gprs_cid_activated(struct ofono_gprs *gprs, unsigned int cid, pri_settings_changed(pri_ctx); } - /* Prevent ofono_gprs_status_notify from changing the 'attached' - * state until after the context has been set to 'active' in - * the pri_read_settings_callback; this prevents a race where - * the connection manager sees the modem as attached before there - * is an active context. - */ - gprs->flags |= GPRS_FLAG_ATTACHING; - gc->driver->read_settings(gc, cid, pri_read_settings_callback, pri_ctx); } @@ -3006,6 +3051,15 @@ void ofono_gprs_detached_notify(struct ofono_gprs *gprs) { DBG("%s", __ofono_atom_get_path(gprs->atom)); + /* + * In case we are attaching let that finish, it will update to the + * correct status. If we fiddle with driver_attach and the + * attach fails, the code will invert back the state to attached, + * which would leave us in an incorrect state. + */ + if (gprs->flags & GPRS_FLAG_ATTACHING) + return; + gprs->driver_attached = FALSE; gprs_attached_update(gprs); @@ -3025,7 +3079,16 @@ void ofono_gprs_status_notify(struct ofono_gprs *gprs, int status) if (status != NETWORK_REGISTRATION_STATUS_REGISTERED && status != NETWORK_REGISTRATION_STATUS_ROAMING) { - gprs_attached_update(gprs); + /* + * For LTE we attached status reflects successful context + * activation. + * Since we in gprs_netreg_update not even try to attach + * to GPRS if we are running on LTE, we can on some modems + * expect the gprs status to be unknown. That must not + * result in detaching... + */ + if (!on_lte(gprs)) + gprs_attached_update(gprs); return; } @@ -3220,6 +3283,7 @@ static void gprs_context_remove(struct ofono_atom *atom) if (gc->gprs) __ofono_gprs_filter_chain_cancel(gc->gprs->filters, gc); + g_free(gc->interface); g_free(gc); } @@ -3295,13 +3359,16 @@ enum ofono_gprs_context_type ofono_gprs_context_get_type( return gc->type; } +const char *ofono_gprs_context_get_interface(struct ofono_gprs_context *gc) +{ + return gc->interface; +} + void ofono_gprs_context_set_interface(struct ofono_gprs_context *gc, const char *interface) { - struct context_settings *settings = gc->settings; - - g_free(settings->interface); - settings->interface = g_strdup(interface); + g_free(gc->interface); + gc->interface = g_strdup(interface); } void ofono_gprs_context_set_ipv4_address(struct ofono_gprs_context *gc, @@ -3615,7 +3682,7 @@ struct ofono_gprs *ofono_gprs_create(struct ofono_modem *modem, } gprs->status = NETWORK_REGISTRATION_STATUS_UNKNOWN; - gprs->netreg_status = NETWORK_REGISTRATION_STATUS_UNKNOWN; + gprs->netreg_status = -1; gprs->pid_map = idmap_new(MAX_CONTEXTS); gprs->filters = __ofono_gprs_filter_chain_new(gprs); @@ -3627,6 +3694,7 @@ static void netreg_watch(struct ofono_atom *atom, void *data) { struct ofono_gprs *gprs = data; + int status; if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED) { gprs_netreg_removed(gprs); @@ -3634,7 +3702,16 @@ static void netreg_watch(struct ofono_atom *atom, } gprs->netreg = __ofono_atom_get_data(atom); - gprs->netreg_status = ofono_netreg_get_status(gprs->netreg); + status = ofono_netreg_get_status(gprs->netreg); + + /* + * If the status is known, assign it, otherwise keep the init value + * to indicate that the netreg atom is not initialised with a known + * value + */ + if (status != NETWORK_REGISTRATION_STATUS_UNKNOWN) + gprs->netreg_status = status; + gprs->status_watch = __ofono_netreg_add_status_watch(gprs->netreg, netreg_status_changed, gprs, NULL); @@ -3848,11 +3925,18 @@ static void gprs_load_settings(struct ofono_gprs *gprs, const char *imsi) storage_sync(imsi, SETTINGS_STORE, gprs->settings); } +static void gprs_list_active_contexts_callback(const struct ofono_error *error, + void *data) +{ + DBG("error = %d", error->type); +} + static void ofono_gprs_finish_register(struct ofono_gprs *gprs) { DBusConnection *conn = ofono_dbus_get_connection(); struct ofono_modem *modem = __ofono_atom_get_modem(gprs->atom); const char *path = __ofono_atom_get_path(gprs->atom); + const struct ofono_gprs_driver *driver = gprs->driver; configure_remaining_contexts(gprs); @@ -3875,6 +3959,12 @@ static void ofono_gprs_finish_register(struct ofono_gprs *gprs) netreg_watch, gprs, NULL); __ofono_atom_register(gprs->atom, gprs_unregister); + + /* Find any context activated during init */ + if (driver->list_active_contexts) + driver->list_active_contexts(gprs, + gprs_list_active_contexts_callback, + gprs); } static void spn_read_cb(const char *spn, const char *dc, void *data) diff --git a/ofono/src/handsfree.c b/ofono/src/handsfree.c index 921e0bd7e..aea5fa5f4 100644 --- a/ofono/src/handsfree.c +++ b/ofono/src/handsfree.c @@ -174,7 +174,7 @@ void ofono_handsfree_battchg_notify(struct ofono_handsfree *hf, unsigned char level) { DBusConnection *conn = ofono_dbus_get_connection(); - const char *path = __ofono_atom_get_path(hf->atom); + const char *path; if (hf == NULL) return; @@ -187,6 +187,7 @@ void ofono_handsfree_battchg_notify(struct ofono_handsfree *hf, if (__ofono_atom_get_registered(hf->atom) == FALSE) return; + path = __ofono_atom_get_path(hf->atom); ofono_dbus_signal_property_changed(conn, path, OFONO_HANDSFREE_INTERFACE, "BatteryChargeLevel", DBUS_TYPE_BYTE, @@ -295,7 +296,7 @@ static void hf_cnum_callback(const struct ofono_error *error, int total, subscriber_number->type = numbers[num].type; strncpy(subscriber_number->number, numbers[num].number, - OFONO_MAX_PHONE_NUMBER_LENGTH + 1); + OFONO_MAX_PHONE_NUMBER_LENGTH); hf->subscriber_numbers = g_slist_prepend(hf->subscriber_numbers, subscriber_number); diff --git a/ofono/src/log.c b/ofono/src/log.c index 999b2d88b..a58472114 100644 --- a/ofono/src/log.c +++ b/ofono/src/log.c @@ -238,7 +238,7 @@ static void print_backtrace(unsigned int offset) if (written < 0) break; - len = read(infd[0], buf, sizeof(buf)); + len = read(infd[0], buf, sizeof(buf) - 1); if (len < 0) break; diff --git a/ofono/src/main.c b/ofono/src/main.c index 5bca6d582..dbb0a13bb 100644 --- a/ofono/src/main.c +++ b/ofono/src/main.c @@ -249,6 +249,7 @@ int main(int argc, char **argv) #ifdef HAVE_ELL l_log_set_stderr(); + l_debug(""); l_debug_enable("*"); l_main_init(); diff --git a/ofono/src/message-waiting.c b/ofono/src/message-waiting.c index a356e9016..6115d495f 100644 --- a/ofono/src/message-waiting.c +++ b/ofono/src/message-waiting.c @@ -753,8 +753,8 @@ static void mw_set_indicator(struct ofono_message_waiting *mw, int profile, efmwis[0] = mw->messages[0].indication ? 0xa : 0x5; if (mw->ef_cphs_mwis_length > 1) - efmwis[1] = mw->messages[1].indication ? 0xa : 0x5 | - mw->messages[3].indication ? 0xa0 : 0x50; + efmwis[1] = (mw->messages[1].indication ? 0xa : 0x5) | + (mw->messages[3].indication ? 0xa0 : 0x50); if (ofono_sim_write(mw->sim_context, SIM_EF_CPHS_MWIS_FILEID, mw_mwis_write_cb, diff --git a/ofono/src/modem.c b/ofono/src/modem.c index e94adeccb..55ab4168c 100644 --- a/ofono/src/modem.c +++ b/ofono/src/modem.c @@ -1150,6 +1150,9 @@ static DBusMessage *modem_set_property(DBusConnection *conn, if (modem->lockdown) return __ofono_error_access_denied(msg); + if (!powered) + __ofono_sim_clear_cached_pins(modem->sim); + err = set_powered(modem, powered); if (err < 0) { if (err != -EINPROGRESS) diff --git a/ofono/src/netmon.c b/ofono/src/netmon.c index 62e0ec0b8..9eacb3ca5 100644 --- a/ofono/src/netmon.c +++ b/ofono/src/netmon.c @@ -50,6 +50,8 @@ struct ofono_netmon { const struct ofono_netmon_driver *driver; DBusMessage *pending; DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter arr; void *driver_data; struct ofono_atom *atom; struct netmon_agent *agent; @@ -69,174 +71,145 @@ static const char *cell_type_to_tech_name(enum ofono_netmon_cell_type type) return NULL; } -void ofono_netmon_serving_cell_notify(struct ofono_netmon *netmon, - enum ofono_netmon_cell_type type, - int info_type, ...) +static void netmon_cell_info_dict_append(DBusMessageIter *dict, + va_list *arglist, int info_type) { - va_list arglist; - DBusMessage *agent_notify = NULL; - DBusMessageIter iter; - DBusMessageIter dict; - enum ofono_netmon_info next_info_type = info_type; - const char *technology = cell_type_to_tech_name(type); char *mcc; char *mnc; int intval; - - if (netmon->pending != NULL) { - netmon->reply = dbus_message_new_method_return(netmon->pending); - dbus_message_iter_init_append(netmon->reply, &iter); - } else if (netmon->agent != NULL) { - agent_notify = netmon_agent_new_method_call(netmon->agent, - "ServingCellInformationChanged"); - - dbus_message_iter_init_append(agent_notify, &iter); - } else - return; - - dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, - OFONO_PROPERTIES_ARRAY_SIGNATURE, - &dict); - - va_start(arglist, info_type); - - if (technology == NULL) - goto done; - - ofono_dbus_dict_append(&dict, "Technology", - DBUS_TYPE_STRING, &technology); + enum ofono_netmon_info next_info_type = info_type; while (next_info_type != OFONO_NETMON_INFO_INVALID) { switch (next_info_type) { case OFONO_NETMON_INFO_MCC: - mcc = va_arg(arglist, char *); + mcc = va_arg(*arglist, char *); if (mcc && strlen(mcc)) - ofono_dbus_dict_append(&dict, + ofono_dbus_dict_append(dict, "MobileCountryCode", DBUS_TYPE_STRING, &mcc); break; case OFONO_NETMON_INFO_MNC: - mnc = va_arg(arglist, char *); + mnc = va_arg(*arglist, char *); if (mnc && strlen(mnc)) - ofono_dbus_dict_append(&dict, + ofono_dbus_dict_append(dict, "MobileNetworkCode", DBUS_TYPE_STRING, &mnc); break; case OFONO_NETMON_INFO_LAC: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "LocationAreaCode", + CELL_INFO_DICT_APPEND(dict, "LocationAreaCode", intval, uint16_t, DBUS_TYPE_UINT16); break; case OFONO_NETMON_INFO_CI: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "CellId", + CELL_INFO_DICT_APPEND(dict, "CellId", intval, uint32_t, DBUS_TYPE_UINT32); break; case OFONO_NETMON_INFO_ARFCN: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "ARFCN", + CELL_INFO_DICT_APPEND(dict, "ARFCN", intval, uint16_t, DBUS_TYPE_UINT16); break; case OFONO_NETMON_INFO_BSIC: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "BSIC", + CELL_INFO_DICT_APPEND(dict, "BSIC", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_RXLEV: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "ReceivedSignalStrength", + CELL_INFO_DICT_APPEND(dict, "ReceivedSignalStrength", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_TIMING_ADVANCE: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "TimingAdvance", + CELL_INFO_DICT_APPEND(dict, "TimingAdvance", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_PSC: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "PrimaryScramblingCode", + CELL_INFO_DICT_APPEND(dict, "PrimaryScramblingCode", intval, uint16_t, DBUS_TYPE_UINT16); break; case OFONO_NETMON_INFO_BER: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "BitErrorRate", + CELL_INFO_DICT_APPEND(dict, "BitErrorRate", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_RSSI: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "Strength", + CELL_INFO_DICT_APPEND(dict, "Strength", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_RSCP: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "ReceivedSignalCodePower", + CELL_INFO_DICT_APPEND(dict, "ReceivedSignalCodePower", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_ECN0: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "ReceivedEnergyRatio", + CELL_INFO_DICT_APPEND(dict, "ReceivedEnergyRatio", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_RSRQ: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, + CELL_INFO_DICT_APPEND(dict, "ReferenceSignalReceivedQuality", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_RSRP: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, + CELL_INFO_DICT_APPEND(dict, "ReferenceSignalReceivedPower", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_EARFCN: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "EARFCN", + CELL_INFO_DICT_APPEND(dict, "EARFCN", intval, uint16_t, DBUS_TYPE_UINT16); break; case OFONO_NETMON_INFO_EBAND: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "EBand", + CELL_INFO_DICT_APPEND(dict, "EBand", intval, uint8_t, DBUS_TYPE_BYTE); break; case OFONO_NETMON_INFO_CQI: - intval = va_arg(arglist, int); + intval = va_arg(*arglist, int); - CELL_INFO_DICT_APPEND(&dict, "ChannelQualityIndicator", + CELL_INFO_DICT_APPEND(dict, "ChannelQualityIndicator", intval, uint8_t, DBUS_TYPE_BYTE); break; @@ -244,8 +217,44 @@ void ofono_netmon_serving_cell_notify(struct ofono_netmon *netmon, break; } - next_info_type = va_arg(arglist, int); + next_info_type = va_arg(*arglist, int); } +} + +void ofono_netmon_serving_cell_notify(struct ofono_netmon *netmon, + enum ofono_netmon_cell_type type, + int info_type, ...) +{ + va_list arglist; + DBusMessage *agent_notify = NULL; + DBusMessageIter iter; + DBusMessageIter dict; + const char *technology = cell_type_to_tech_name(type); + + if (netmon->pending != NULL) { + netmon->reply = dbus_message_new_method_return(netmon->pending); + dbus_message_iter_init_append(netmon->reply, &iter); + } else if (netmon->agent != NULL) { + agent_notify = netmon_agent_new_method_call(netmon->agent, + "ServingCellInformationChanged"); + + dbus_message_iter_init_append(agent_notify, &iter); + } else + return; + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dict); + + va_start(arglist, info_type); + + if (technology == NULL) + goto done; + + ofono_dbus_dict_append(&dict, "Technology", + DBUS_TYPE_STRING, &technology); + + netmon_cell_info_dict_append(&dict, &arglist, info_type); done: va_end(arglist); @@ -288,7 +297,7 @@ static DBusMessage *netmon_get_serving_cell_info(DBusConnection *conn, { struct ofono_netmon *netmon = data; - if (!netmon->driver && !netmon->driver->request_update) + if (!netmon->driver->request_update) return __ofono_error_not_implemented(msg); if (netmon->pending) @@ -403,6 +412,109 @@ static DBusMessage *netmon_unregister_agent(DBusConnection *conn, return dbus_message_new_method_return(msg); } + +void ofono_netmon_neighbouring_cell_notify(struct ofono_netmon *netmon, + enum ofono_netmon_cell_type type, + int info_type, ...) +{ + va_list arglist; + DBusMessageIter dict; + DBusMessageIter strct; + const char *tech = cell_type_to_tech_name(type); + + if (netmon->pending == NULL) + return; + + if (!netmon->reply) { + netmon->reply = dbus_message_new_method_return(netmon->pending); + dbus_message_iter_init_append(netmon->reply, &netmon->iter); + + dbus_message_iter_open_container(&netmon->iter, DBUS_TYPE_ARRAY, + DBUS_STRUCT_BEGIN_CHAR_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING + DBUS_STRUCT_END_CHAR_AS_STRING, + &netmon->arr); + } + + tech = cell_type_to_tech_name(type); + + dbus_message_iter_open_container(&netmon->arr, DBUS_TYPE_STRUCT, + NULL, &strct); + dbus_message_iter_open_container(&strct, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dict); + + va_start(arglist, info_type); + + if (tech == NULL) + goto done; + + ofono_dbus_dict_append(&dict, "Technology", + DBUS_TYPE_STRING, &tech); + + netmon_cell_info_dict_append(&dict, &arglist, info_type); + +done: + va_end(arglist); + + dbus_message_iter_close_container(&strct, &dict); + dbus_message_iter_close_container(&netmon->arr, &strct); +} + +static void neighbouring_cell_info_callback(const struct ofono_error *error, + void *data) +{ + struct ofono_netmon *netmon = data; + DBusMessage *reply = netmon->reply; + + DBG(""); + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + if (reply) + dbus_message_unref(reply); + + reply = __ofono_error_failed(netmon->pending); + } else if (!reply) { + DBusMessageIter iter; + DBusMessageIter dict; + + reply = dbus_message_new_method_return(netmon->pending); + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dict); + dbus_message_iter_close_container(&iter, &dict); + } else { + dbus_message_iter_close_container(&netmon->iter, &netmon->arr); + } + + netmon->reply = NULL; + __ofono_dbus_pending_reply(&netmon->pending, reply); +} + +static DBusMessage *netmon_get_neighbouring_cell_info(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ofono_netmon *netmon = data; + + if (!netmon->driver->neighbouring_cell_update) + return __ofono_error_not_implemented(msg); + + if (netmon->pending) + return __ofono_error_busy(msg); + + netmon->pending = dbus_message_ref(msg); + + netmon->driver->neighbouring_cell_update(netmon, + neighbouring_cell_info_callback, netmon); + + return NULL; +} + static const GDBusMethodTable netmon_methods[] = { { GDBUS_ASYNC_METHOD("GetServingCellInformation", NULL, GDBUS_ARGS({ "cellinfo", "a{sv}" }), @@ -413,6 +525,9 @@ static const GDBusMethodTable netmon_methods[] = { { GDBUS_METHOD("UnregisterAgent", GDBUS_ARGS({ "agent", "o" }), NULL, netmon_unregister_agent) }, + { GDBUS_ASYNC_METHOD("GetNeighbouringCellInformation", + NULL, GDBUS_ARGS({ "cellinfo", "a(a{sv})" }), + netmon_get_neighbouring_cell_info) }, { } }; diff --git a/ofono/src/network.c b/ofono/src/network.c index 2882e2d24..4d1775f60 100644 --- a/ofono/src/network.c +++ b/ofono/src/network.c @@ -1391,8 +1391,8 @@ void ofono_netreg_status_notify(struct ofono_netreg *netreg, int status, if (netreg == NULL) return; - DBG("%s status %d tech %d", __ofono_atom_get_path(netreg->atom), - status, tech); + DBG("%s status %d tech %d lac %d ci %d", + __ofono_atom_get_path(netreg->atom), status, tech, lac, ci); if (netreg->status != status) { struct ofono_modem *modem; @@ -1447,6 +1447,11 @@ void ofono_netreg_time_notify(struct ofono_netreg *netreg, if (info == NULL) return; + DBG("net time %d-%02d-%02d %02d:%02d:%02d utcoff %d dst %d", + info->year, info->mon, info->mday, + info->hour, info->min, info->sec, + info->utcoff, info->dst); + __ofono_nettime_info_received(modem, info); } diff --git a/ofono/src/ofono.h b/ofono/src/ofono.h index 0d15352ba..f9455a66a 100644 --- a/ofono/src/ofono.h +++ b/ofono/src/ofono.h @@ -428,6 +428,7 @@ const struct sim_aid *__ofono_sim_session_get_aid( struct ofono_sim_aid_session *session); const char *__ofono_sim_get_impi(struct ofono_sim *sim); +void __ofono_sim_clear_cached_pins(struct ofono_sim *sim); #include diff --git a/ofono/src/phonebook.c b/ofono/src/phonebook.c index 391b7d306..007cb76eb 100644 --- a/ofono/src/phonebook.c +++ b/ofono/src/phonebook.c @@ -179,7 +179,7 @@ static void vcard_printf_number(GString *vcards, const char *number, int type, if ((type == TYPE_INTERNATIONAL) && (number[0] != '+')) intl = "+"; - snprintf(buf, sizeof(buf), "TEL;TYPE=\%s%s:\%s\%s", pref, + snprintf(buf, sizeof(buf), "TEL;TYPE=%s%s:%s%s", pref, category_string, intl, number); vcard_printf(vcards, buf, number); } diff --git a/ofono/src/radio-settings.c b/ofono/src/radio-settings.c index 9063a8fa2..6d4169a5a 100644 --- a/ofono/src/radio-settings.c +++ b/ofono/src/radio-settings.c @@ -86,9 +86,15 @@ const char *ofono_radio_access_mode_to_string(enum ofono_radio_access_mode m) return "umts"; case OFONO_RADIO_ACCESS_MODE_LTE: return "lte"; - default: - return NULL; } + + if (m == (OFONO_RADIO_ACCESS_MODE_UMTS|OFONO_RADIO_ACCESS_MODE_GSM)) + return "umts,gsm"; + + if (m == (OFONO_RADIO_ACCESS_MODE_LTE|OFONO_RADIO_ACCESS_MODE_UMTS)) + return "lte,umts"; + + return NULL; } #define radio_access_mode_from_string ofono_radio_access_mode_from_string @@ -110,6 +116,12 @@ ofono_bool_t ofono_radio_access_mode_from_string(const char *str, } else if (g_str_equal(str, "lte")) { *mode = OFONO_RADIO_ACCESS_MODE_LTE; return TRUE; + } else if (g_str_equal(str, "umts,gsm")) { + *mode = OFONO_RADIO_ACCESS_MODE_UMTS|OFONO_RADIO_ACCESS_MODE_GSM; + return TRUE; + } else if (g_str_equal(str, "lte,umts")) { + *mode = OFONO_RADIO_ACCESS_MODE_LTE|OFONO_RADIO_ACCESS_MODE_UMTS; + return TRUE; } return FALSE; diff --git a/ofono/src/sim-auth.c b/ofono/src/sim-auth.c index c23b44fba..269b38933 100644 --- a/ofono/src/sim-auth.c +++ b/ofono/src/sim-auth.c @@ -676,13 +676,13 @@ static char *build_nai(const char *imsi) char mnc[3]; char *nai; - strncpy(mcc, imsi, 3); + memcpy(mcc, imsi, 3); if (strlen(imsi) == 16) { - strncpy(mnc, imsi + 3, 3); + memcpy(mnc, imsi + 3, 3); } else { mnc[0] = '0'; - strncpy(mnc + 1, imsi + 3, 2); + memcpy(mnc + 1, imsi + 3, 2); } nai = g_strdup_printf("%s@ims.mnc%.3s.mcc%.3s.3gppnetwork.org", diff --git a/ofono/src/sim.c b/ofono/src/sim.c index dd2a20287..32562d0ab 100644 --- a/ofono/src/sim.c +++ b/ofono/src/sim.c @@ -132,6 +132,10 @@ struct ofono_sim { struct ofono_atom *atom; unsigned int hfp_watch; + unsigned int card_slot_count; + unsigned int active_card_slot; + unsigned int pending_active_card_slot; + GSList *aid_sessions; GSList *aid_list; char *impi; @@ -144,6 +148,11 @@ struct ofono_sim { bool wait_initialized : 1; }; +struct cached_pin { + char *id; + char *pin; +}; + struct msisdn_set_request { struct ofono_sim *sim; int pending; @@ -179,6 +188,8 @@ static void sim_own_numbers_update(struct ofono_sim *sim); static GSList *g_drivers = NULL; +static GSList *cached_pins = NULL; + static const char *sim_passwd_name(enum ofono_sim_password_type type) { return passwd_name[type]; @@ -558,6 +569,13 @@ static DBusMessage *sim_get_properties(DBusConnection *conn, get_pin_retries(sim, &pin_retries_dict, &dbus_retries); ofono_dbus_dict_append_dict(&dict, "Retries", DBUS_TYPE_BYTE, &pin_retries_dict); + + ofono_dbus_dict_append(&dict, "CardSlotCount", DBUS_TYPE_UINT32, + &sim->card_slot_count); + + ofono_dbus_dict_append(&dict, "ActiveCardSlot", DBUS_TYPE_UINT32, + &sim->active_card_slot); + g_free(pin_retries_dict); g_free(dbus_retries); @@ -567,6 +585,72 @@ static DBusMessage *sim_get_properties(DBusConnection *conn, return reply; } +static struct cached_pin *pin_cache_lookup(const char *iccid) +{ + struct cached_pin *c; + GSList *l; + + if (cached_pins == NULL) + return NULL; + + for (l = cached_pins; l; l = l->next) { + c = l->data; + + if (g_strcmp0(iccid, c->id) == 0) + return c; + } + + return NULL; +} + +static void pin_cache_update(const char *iccid, const char *pin) +{ + struct cached_pin *pin_cached = pin_cache_lookup(iccid); + struct cached_pin *cpins; + + if (pin_cached != NULL) { + g_free(pin_cached->pin); + pin_cached->pin = g_strdup(pin); + return; + } + + cpins = g_new0(struct cached_pin, 1); + + cpins->id = g_strdup(iccid); + cpins->pin = g_strdup(pin); + cached_pins = g_slist_prepend(cached_pins, cpins); +} + +static void pin_cache_remove(const char *iccid) +{ + struct cached_pin *pin_cached = pin_cache_lookup(iccid); + + if (pin_cached == NULL) + return; + + cached_pins = g_slist_remove(cached_pins, pin_cached); + + g_free(pin_cached->pin); + g_free(pin_cached->id); + g_free(pin_cached); +} + +static void pin_cache_enter_cb(const struct ofono_error *error, void *data) +{ + struct ofono_sim *sim = data; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + pin_cache_remove(sim->iccid); + + __ofono_sim_recheck_pin(sim); + + return; + } + + sim->wait_initialized = true; + DBG("Waiting for ofono_sim_initialized_notify"); +} + static void sim_pin_retries_query_cb(const struct ofono_error *error, int retries[OFONO_SIM_PASSWORD_INVALID], void *data) @@ -683,6 +767,36 @@ static gboolean sim_allow(DBusMessage *msg, OFONO_DBUS_ACCESS_INTF_SIMMGR, method, arg); } +static void sim_set_slot_callback(const struct ofono_error *error, void *data) +{ + struct ofono_sim *sim = data; + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path = __ofono_atom_get_path(sim->atom); + DBusMessage *reply; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + DBG("Error setting radio access mode"); + + sim->pending_active_card_slot = sim->active_card_slot; + + reply = __ofono_error_from_error(error, sim->pending); + __ofono_dbus_pending_reply(&sim->pending, reply); + + return; + } + + sim->active_card_slot = sim->pending_active_card_slot; + + reply = dbus_message_new_method_return(sim->pending); + __ofono_dbus_pending_reply(&sim->pending, reply); + + ofono_dbus_signal_property_changed(conn, path, + OFONO_SIM_MANAGER_INTERFACE, + "ActiveCardSlot", + DBUS_TYPE_UINT32, + &sim->active_card_slot); +} + static DBusMessage *sim_set_property(DBusConnection *conn, DBusMessage *msg, void *data) { @@ -753,6 +867,37 @@ static DBusMessage *sim_set_property(DBusConnection *conn, DBusMessage *msg, if (set_ok) return NULL; + } else if (!strcmp(name, "ActiveCardSlot")) { + dbus_uint32_t value; + + dbus_message_iter_next(&iter); + + if (sim->driver->set_active_card_slot == NULL) + return __ofono_error_not_implemented(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &var); + + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_UINT32) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &value); + + if (value <= 0 || value > sim->card_slot_count) + return __ofono_error_invalid_args(msg); + + if (sim->active_card_slot == value) + return dbus_message_new_method_return(msg); + + sim->pending = dbus_message_ref(msg); + sim->pending_active_card_slot = value; + + sim->driver->set_active_card_slot(sim, value - 1, + sim_set_slot_callback, + sim); + return NULL; } return __ofono_error_invalid_args(msg); @@ -785,6 +930,11 @@ static void sim_locked_cb(struct ofono_sim *sim, gboolean locked) OFONO_SIM_MANAGER_INTERFACE, "LockedPins", DBUS_TYPE_STRING, &locked_pins); + + /* Cache pin only for SIM PIN type */ + if (g_strcmp0(typestr, "pin") == 0) + pin_cache_update(sim->iccid, pin); + g_strfreev(locked_pins); sim_pin_retries_check(sim); @@ -886,6 +1036,14 @@ static DBusMessage *sim_unlock_pin(DBusConnection *conn, DBusMessage *msg, static void sim_change_pin_cb(const struct ofono_error *error, void *data) { struct ofono_sim *sim = data; + const char *typestr; + const char *old; + const char *new; + + dbus_message_get_args(sim->pending, NULL, DBUS_TYPE_STRING, &typestr, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID); if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { __ofono_dbus_pending_reply(&sim->pending, @@ -896,6 +1054,10 @@ static void sim_change_pin_cb(const struct ofono_error *error, void *data) return; } + /* Cache pin only for SIM PIN type */ + if (g_strcmp0(typestr, "pin") == 0) + pin_cache_update(sim->iccid, new); + __ofono_dbus_pending_reply(&sim->pending, dbus_message_new_method_return(sim->pending)); @@ -950,8 +1112,14 @@ static DBusMessage *sim_change_pin(DBusConnection *conn, DBusMessage *msg, static void sim_enter_pin_cb(const struct ofono_error *error, void *data) { struct ofono_sim *sim = data; + const char *typestr; + const char *pin; DBusMessage *reply; + dbus_message_get_args(sim->pending, NULL, DBUS_TYPE_STRING, &typestr, + DBUS_TYPE_STRING, &pin, + DBUS_TYPE_INVALID); + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) reply = __ofono_error_failed(sim->pending); else @@ -963,6 +1131,10 @@ static void sim_enter_pin_cb(const struct ofono_error *error, void *data) if (sim->initialized || error->type != OFONO_ERROR_TYPE_NO_ERROR) goto recheck; + /* Cache pin only for SIM PIN type */ + if (g_strcmp0(typestr, "pin") == 0) + pin_cache_update(sim->iccid, pin); + if (sim->pin_type == OFONO_SIM_PASSWORD_SIM_PIN || sim->pin_type == OFONO_SIM_PASSWORD_SIM_PUK) { sim->wait_initialized = true; @@ -2431,11 +2603,13 @@ struct ofono_sim_context *ofono_sim_context_create(struct ofono_sim *sim) struct ofono_sim_context *ofono_sim_context_create_isim( struct ofono_sim *sim) { - GSList *iter = sim->aid_sessions; + GSList *iter; if (sim == NULL || sim->simfs_isim == NULL) return NULL; + iter = sim->aid_sessions; + /* Find the AID */ while (iter) { struct ofono_sim_aid_session *session = iter->data; @@ -2918,6 +3092,8 @@ void ofono_sim_inserted_notify(struct ofono_sim *sim, ofono_bool_t inserted) sim->pin_retries[OFONO_SIM_PASSWORD_SIM_PIN2] = -1; sim->pin_retries[OFONO_SIM_PASSWORD_SIM_PUK2] = -1; + pin_cache_remove(sim->iccid); + sim_free_state(sim); } } @@ -3191,7 +3367,8 @@ static void sim_pin_query_cb(const struct ofono_error *error, struct ofono_sim *sim = data; DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(sim->atom); - const char *pin_name; + struct cached_pin *cpins = pin_cache_lookup(sim->iccid); + const char *pin_name = sim_passwd_name(pin_type); char **locked_pins; gboolean lock_changed; @@ -3204,7 +3381,6 @@ static void sim_pin_query_cb(const struct ofono_error *error, if (sim->pin_type != pin_type) { sim->pin_type = pin_type; - pin_name = sim_passwd_name(pin_type); if (pin_type != OFONO_SIM_PASSWORD_NONE && password_is_pin(pin_type) == FALSE) @@ -3235,6 +3411,10 @@ static void sim_pin_query_cb(const struct ofono_error *error, &pin_name); } + if (g_strcmp0(pin_name, "pin") == 0 && cpins != NULL) + sim->driver->send_passwd(sim, cpins->pin, + pin_cache_enter_cb, sim); + switch (pin_type) { case OFONO_SIM_PASSWORD_NONE: case OFONO_SIM_PASSWORD_SIM_PIN2: @@ -3377,6 +3557,9 @@ struct ofono_sim *ofono_sim_create(struct ofono_modem *modem, for (i = 0; i < OFONO_SIM_PASSWORD_INVALID; i++) sim->pin_retries[i] = -1; + sim->active_card_slot = 1; + sim->card_slot_count = 1; + for (l = g_drivers; l; l = l->next) { const struct ofono_sim_driver *drv = l->data; @@ -3475,6 +3658,14 @@ void ofono_sim_register(struct ofono_sim *sim) __ofono_atom_register(sim->atom, sim_unregister); } +void __ofono_sim_clear_cached_pins(struct ofono_sim *sim) +{ + if (cached_pins == NULL) + return; + + pin_cache_remove(sim->iccid); +} + void ofono_sim_remove(struct ofono_sim *sim) { __ofono_atom_free(sim->atom); @@ -3890,3 +4081,15 @@ int ofono_sim_logical_access(struct ofono_sim *sim, int session_id, return 0; } + +void ofono_sim_set_card_slot_count(struct ofono_sim *sim, unsigned int val) +{ + if (sim) + sim->card_slot_count = val; +} + +void ofono_sim_set_active_card_slot(struct ofono_sim *sim, unsigned int val) +{ + if (sim) + sim->active_card_slot = val; +} diff --git a/ofono/src/siri.c b/ofono/src/siri.c index 88888dcf9..82715d384 100644 --- a/ofono/src/siri.c +++ b/ofono/src/siri.c @@ -54,7 +54,7 @@ struct ofono_siri { void ofono_siri_set_status(struct ofono_siri *siri, int value) { DBusConnection *conn = ofono_dbus_get_connection(); - const char *path = __ofono_atom_get_path(siri->atom); + const char *path; dbus_bool_t siri_status; if (siri == NULL) @@ -70,6 +70,7 @@ void ofono_siri_set_status(struct ofono_siri *siri, int value) if (__ofono_atom_get_registered(siri->atom) == FALSE) return; + path = __ofono_atom_get_path(siri->atom); ofono_dbus_signal_property_changed(conn, path, OFONO_SIRI_INTERFACE, "Enabled", DBUS_TYPE_BOOLEAN, &siri_status); diff --git a/ofono/src/stkutil.c b/ofono/src/stkutil.c index da5ecd5ec..dfce9ec4f 100644 --- a/ofono/src/stkutil.c +++ b/ofono/src/stkutil.c @@ -5417,8 +5417,10 @@ static bool build_dataobj(struct stk_tlv_builder *tlv, const void *data = va_arg(args, const void *); bool cr = (flags & DATAOBJ_FLAG_CR) ? true : false; - if (!builder_func(tlv, data, cr)) + if (!builder_func(tlv, data, cr)) { + va_end(args); return false; + } builder_func = va_arg(args, dataobj_writer); } diff --git a/ofono/src/voicecall.c b/ofono/src/voicecall.c index 5e956dfae..a49488ef9 100644 --- a/ofono/src/voicecall.c +++ b/ofono/src/voicecall.c @@ -4032,7 +4032,7 @@ static void emulator_atd_cb(struct ofono_emulator *em, emulator_dial(em, vc, num); } else { - strncpy(number, str, len - 1); + memcpy(number, str, len - 1); number[len - 1] = '\0'; emulator_dial(em, vc, number); @@ -4469,6 +4469,9 @@ void __ofono_voicecall_tone_cancel(struct ofono_voicecall *vc, int id) if (entry->id == id) break; + if (!entry) + return; + tone_request_finish(vc, entry, 0, FALSE); /* diff --git a/ofono/test/dial-number b/ofono/test/dial-number index fe5adad3c..4c0ee1429 100755 --- a/ofono/test/dial-number +++ b/ofono/test/dial-number @@ -4,7 +4,8 @@ import sys import dbus if (len(sys.argv) < 2): - print("Usage: %s [modem] [hide_callerid]" % (sys.argv[0])) + print("Usage: %s [modem] [enabled|disabled|default]" % (sys.argv[0])) + print("Last argument enables/disables callerid") sys.exit(1) bus = dbus.SystemBus() diff --git a/ofono/test/process-context-settings b/ofono/test/process-context-settings index 1d30b30df..7ffb12b30 100755 --- a/ofono/test/process-context-settings +++ b/ofono/test/process-context-settings @@ -1,6 +1,7 @@ #!/usr/bin/python3 import os +import sys import dbus bus = dbus.SystemBus() @@ -23,13 +24,14 @@ for path, properties in modems: if properties["Active"] == dbus.Boolean(0): continue - print("Configuring %s" % (path)) + print("Configuring %s" % (path), file=sys.stderr) settings = properties["Settings"] interface = settings["Interface"] if settings["Method"] == "dhcp": - print(" Run DHCP on interface %s" % (interface)) + print(" Run DHCP on interface %s" % (interface), + file=sys.stderr) else: address = settings["Address"] try: @@ -37,18 +39,22 @@ for path, properties in modems: except: gateway = "0.0.0.0"; - print(" Interface is %s" % (interface)) - print(" IP address is %s" % (address)) - print(" Gateway is %s" % (gateway)) + print(" Interface is %s" % (interface), + file=sys.stderr) + print(" IP address is %s" % (address), + file=sys.stderr) + print(" Gateway is %s" % (gateway), + file=sys.stderr) - cmd = "ifconfig " + interface + " " + address - cmd += " netmask 255.255.255.255" + cmd = "ip address add dev " + interface + " " + address + cmd += "/32" os.system(cmd); for i in settings["DomainNameServers"]: - print(" Nameserver is %s" % (i)) + print(" Nameserver is %s" % (i), + file=sys.stderr) - cmd = "route add -host " + i + cmd = "ip route add " + i cmd +=" dev " + interface os.system(cmd); - print('') + print('', file=sys.stderr)