diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:15 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:15 -0800 |
commit | 00f06fc3fdb05d4276e76091cacb42b6f6862222 (patch) | |
tree | 91c65b301062ebdef8863d9b82fc6d372bbdcba1 /reference-ril | |
parent | c2efc51d848d68147320a5983954f9c76428885c (diff) | |
download | android_hardware_ril-00f06fc3fdb05d4276e76091cacb42b6f6862222.tar.gz android_hardware_ril-00f06fc3fdb05d4276e76091cacb42b6f6862222.tar.bz2 android_hardware_ril-00f06fc3fdb05d4276e76091cacb42b6f6862222.zip |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'reference-ril')
-rw-r--r-- | reference-ril/Android.mk | 48 | ||||
-rw-r--r-- | reference-ril/MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | reference-ril/NOTICE | 190 | ||||
-rw-r--r-- | reference-ril/at_tok.c | 190 | ||||
-rw-r--r-- | reference-ril/at_tok.h | 30 | ||||
-rw-r--r-- | reference-ril/atchannel.c | 1018 | ||||
-rw-r--r-- | reference-ril/atchannel.h | 124 | ||||
-rw-r--r-- | reference-ril/misc.c | 29 | ||||
-rw-r--r-- | reference-ril/misc.h | 19 | ||||
-rw-r--r-- | reference-ril/reference-ril.c | 2052 |
10 files changed, 3700 insertions, 0 deletions
diff --git a/reference-ril/Android.mk b/reference-ril/Android.mk new file mode 100644 index 0000000..7ec1f97 --- /dev/null +++ b/reference-ril/Android.mk @@ -0,0 +1,48 @@ +# Copyright 2006 The Android Open Source Project + +# XXX using libutils for simulator build only... +# +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + reference-ril.c \ + atchannel.c \ + misc.c \ + at_tok.c + +LOCAL_SHARED_LIBRARIES := \ + libcutils libutils libril + + # for asprinf +LOCAL_CFLAGS := -D_GNU_SOURCE + +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) + +ifeq ($(TARGET_DEVICE),sooner) + LOCAL_CFLAGS += -DOMAP_CSMI_POWER_CONTROL -DUSE_TI_COMMANDS +endif + +ifeq ($(TARGET_DEVICE),surf) + LOCAL_CFLAGS += -DPOLL_CALL_STATE -DUSE_QMI +endif + +ifeq ($(TARGET_DEVICE),dream) + LOCAL_CFLAGS += -DPOLL_CALL_STATE -DUSE_QMI +endif + +ifeq (foo,foo) + #build shared library + LOCAL_SHARED_LIBRARIES += \ + libcutils libutils + LOCAL_LDLIBS += -lpthread + LOCAL_CFLAGS += -DRIL_SHLIB + LOCAL_MODULE:= libreference-ril + include $(BUILD_SHARED_LIBRARY) +else + #build executable + LOCAL_SHARED_LIBRARIES += \ + libril + LOCAL_MODULE:= reference-ril + include $(BUILD_EXECUTABLE) +endif diff --git a/reference-ril/MODULE_LICENSE_APACHE2 b/reference-ril/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/reference-ril/MODULE_LICENSE_APACHE2 diff --git a/reference-ril/NOTICE b/reference-ril/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/reference-ril/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/reference-ril/at_tok.c b/reference-ril/at_tok.c new file mode 100644 index 0000000..11e0cba --- /dev/null +++ b/reference-ril/at_tok.c @@ -0,0 +1,190 @@ +/* //device/system/reference-ril/at_tok.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "at_tok.h" +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +/** + * Starts tokenizing an AT response string + * returns -1 if this is not a valid response string, 0 on success. + * updates *p_cur with current position + */ +int at_tok_start(char **p_cur) +{ + if (*p_cur == NULL) { + return -1; + } + + // skip prefix + // consume "^[^:]:" + + *p_cur = strchr(*p_cur, ':'); + + if (*p_cur == NULL) { + return -1; + } + + (*p_cur)++; + + return 0; +} + +static void skipWhiteSpace(char **p_cur) +{ + if (*p_cur == NULL) return; + + while (**p_cur != '\0' && isspace(**p_cur)) { + (*p_cur)++; + } +} + +static void skipNextComma(char **p_cur) +{ + if (*p_cur == NULL) return; + + while (**p_cur != '\0' && **p_cur != ',') { + (*p_cur)++; + } + + if (**p_cur == ',') { + (*p_cur)++; + } +} + +static char * nextTok(char **p_cur) +{ + char *ret = NULL; + + skipWhiteSpace(p_cur); + + if (*p_cur == NULL) { + ret = NULL; + } else if (**p_cur == '"') { + (*p_cur)++; + ret = strsep(p_cur, "\""); + skipNextComma(p_cur); + } else { + ret = strsep(p_cur, ","); + } + + return ret; +} + + +/** + * Parses the next integer in the AT response line and places it in *p_out + * returns 0 on success and -1 on fail + * updates *p_cur + * "base" is the same as the base param in strtol + */ + +static int at_tok_nextint_base(char **p_cur, int *p_out, int base, int uns) +{ + char *ret; + + if (*p_cur == NULL) { + return -1; + } + + ret = nextTok(p_cur); + + if (ret == NULL) { + return -1; + } else { + long l; + char *end; + + if (uns) + l = strtoul(ret, &end, base); + else + l = strtol(ret, &end, base); + + *p_out = (int)l; + + if (end == ret) { + return -1; + } + } + + return 0; +} + +/** + * Parses the next base 10 integer in the AT response line + * and places it in *p_out + * returns 0 on success and -1 on fail + * updates *p_cur + */ +int at_tok_nextint(char **p_cur, int *p_out) +{ + return at_tok_nextint_base(p_cur, p_out, 10, 0); +} + +/** + * Parses the next base 16 integer in the AT response line + * and places it in *p_out + * returns 0 on success and -1 on fail + * updates *p_cur + */ +int at_tok_nexthexint(char **p_cur, int *p_out) +{ + return at_tok_nextint_base(p_cur, p_out, 16, 1); +} + +int at_tok_nextbool(char **p_cur, char *p_out) +{ + int ret; + int result; + + ret = at_tok_nextint(p_cur, &result); + + if (ret < 0) { + return -1; + } + + // booleans should be 0 or 1 + if (!(result == 0 || result == 1)) { + return -1; + } + + if (p_out != NULL) { + *p_out = (char)result; + } + + return ret; +} + +int at_tok_nextstr(char **p_cur, char **p_out) +{ + if (*p_cur == NULL) { + return -1; + } + + *p_out = nextTok(p_cur); + + return 0; +} + +/** returns 1 on "has more tokens" and 0 if no */ +int at_tok_hasmore(char **p_cur) +{ + return ! (*p_cur == NULL || **p_cur == '\0'); +} + + diff --git a/reference-ril/at_tok.h b/reference-ril/at_tok.h new file mode 100644 index 0000000..42bb4c9 --- /dev/null +++ b/reference-ril/at_tok.h @@ -0,0 +1,30 @@ +/* //device/system/reference-ril/at_tok.h +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef AT_TOK_H +#define AT_TOK_H 1 + +int at_tok_start(char **p_cur); +int at_tok_nextint(char **p_cur, int *p_out); +int at_tok_nexthexint(char **p_cur, int *p_out); + +int at_tok_nextbool(char **p_cur, char *p_out); +int at_tok_nextstr(char **p_cur, char **out); + +int at_tok_hasmore(char **p_cur); + +#endif /*AT_TOK_H */ diff --git a/reference-ril/atchannel.c b/reference-ril/atchannel.c new file mode 100644 index 0000000..f878b35 --- /dev/null +++ b/reference-ril/atchannel.c @@ -0,0 +1,1018 @@ +/* //device/system/reference-ril/atchannel.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "atchannel.h" +#include "at_tok.h" + +#include <stdio.h> +#include <string.h> +#include <pthread.h> +#include <ctype.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#define LOG_NDEBUG 0 +#define LOG_TAG "AT" +#include <utils/Log.h> + +#ifdef HAVE_ANDROID_OS +/* for IOCTL's */ +#include <linux/omap_csmi.h> +#endif /*HAVE_ANDROID_OS*/ + +#include "misc.h" + +#ifdef HAVE_ANDROID_OS +#define USE_NP 1 +#endif /* HAVE_ANDROID_OS */ + + +#define NUM_ELEMS(x) (sizeof(x)/sizeof(x[0])) + +#define MAX_AT_RESPONSE (8 * 1024) +#define HANDSHAKE_RETRY_COUNT 8 +#define HANDSHAKE_TIMEOUT_MSEC 250 + +static pthread_t s_tid_reader; +static int s_fd = -1; /* fd of the AT channel */ +static ATUnsolHandler s_unsolHandler; + +/* for input buffering */ + +static char s_ATBuffer[MAX_AT_RESPONSE+1]; +static char *s_ATBufferCur = s_ATBuffer; + +static int s_ackPowerIoctl; /* true if TTY has android byte-count + handshake for low power*/ +static int s_readCount = 0; + +#if AT_DEBUG +void AT_DUMP(const char* prefix, const char* buff, int len) +{ + if (len < 0) + len = strlen(buff); + LOGD("%.*s", len, buff); +} +#endif + +/* + * for current pending command + * these are protected by s_commandmutex + */ + +static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER; + +static ATCommandType s_type; +static const char *s_responsePrefix = NULL; +static const char *s_smsPDU = NULL; +static ATResponse *sp_response = NULL; + +static void (*s_onTimeout)(void) = NULL; +static void (*s_onReaderClosed)(void) = NULL; +static int s_readerClosed; + +static void onReaderClosed(); +static int writeCtrlZ (const char *s); +static int writeline (const char *s); + +#ifndef USE_NP +static void setTimespecRelative(struct timespec *p_ts, long long msec) +{ + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) NULL); + + /* what's really funny about this is that I know + pthread_cond_timedwait just turns around and makes this + a relative time again */ + p_ts->tv_sec = tv.tv_sec + (msec / 1000); + p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L; +} +#endif /*USE_NP*/ + +static void sleepMsec(long long msec) +{ + struct timespec ts; + int err; + + ts.tv_sec = (msec / 1000); + ts.tv_nsec = (msec % 1000) * 1000 * 1000; + + do { + err = nanosleep (&ts, &ts); + } while (err < 0 && errno == EINTR); +} + + + +/** add an intermediate response to sp_response*/ +static void addIntermediate(const char *line) +{ + ATLine *p_new; + + p_new = (ATLine *) malloc(sizeof(ATLine)); + + p_new->line = strdup(line); + + /* note: this adds to the head of the list, so the list + will be in reverse order of lines received. the order is flipped + again before passing on to the command issuer */ + p_new->p_next = sp_response->p_intermediates; + sp_response->p_intermediates = p_new; +} + + +/** + * returns 1 if line is a final response indicating error + * See 27.007 annex B + * WARNING: NO CARRIER and others are sometimes unsolicited + */ +static const char * s_finalResponsesError[] = { + "ERROR", + "+CMS ERROR:", + "+CME ERROR:", + "NO CARRIER", /* sometimes! */ + "NO ANSWER", + "NO DIALTONE", +}; +static int isFinalResponseError(const char *line) +{ + size_t i; + + for (i = 0 ; i < NUM_ELEMS(s_finalResponsesError) ; i++) { + if (strStartsWith(line, s_finalResponsesError[i])) { + return 1; + } + } + + return 0; +} + +/** + * returns 1 if line is a final response indicating success + * See 27.007 annex B + * WARNING: NO CARRIER and others are sometimes unsolicited + */ +static const char * s_finalResponsesSuccess[] = { + "OK", + "CONNECT" /* some stacks start up data on another channel */ +}; +static int isFinalResponseSuccess(const char *line) +{ + size_t i; + + for (i = 0 ; i < NUM_ELEMS(s_finalResponsesSuccess) ; i++) { + if (strStartsWith(line, s_finalResponsesSuccess[i])) { + return 1; + } + } + + return 0; +} + +/** + * returns 1 if line is a final response, either error or success + * See 27.007 annex B + * WARNING: NO CARRIER and others are sometimes unsolicited + */ +static int isFinalResponse(const char *line) +{ + return isFinalResponseSuccess(line) || isFinalResponseError(line); +} + + +/** + * returns 1 if line is the first line in (what will be) a two-line + * SMS unsolicited response + */ +static const char * s_smsUnsoliciteds[] = { + "+CMT:", + "+CDS:", + "+CBM:" +}; +static int isSMSUnsolicited(const char *line) +{ + size_t i; + + for (i = 0 ; i < NUM_ELEMS(s_smsUnsoliciteds) ; i++) { + if (strStartsWith(line, s_smsUnsoliciteds[i])) { + return 1; + } + } + + return 0; +} + + +/** assumes s_commandmutex is held */ +static void handleFinalResponse(const char *line) +{ + sp_response->finalResponse = strdup(line); + + pthread_cond_signal(&s_commandcond); +} + +static void handleUnsolicited(const char *line) +{ + if (s_unsolHandler != NULL) { + s_unsolHandler(line, NULL); + } +} + +static void processLine(const char *line) +{ + pthread_mutex_lock(&s_commandmutex); + + if (sp_response == NULL) { + /* no command pending */ + handleUnsolicited(line); + } else if (isFinalResponseSuccess(line)) { + sp_response->success = 1; + handleFinalResponse(line); + } else if (isFinalResponseError(line)) { + sp_response->success = 0; + handleFinalResponse(line); + } else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) { + // See eg. TS 27.005 4.3 + // Commands like AT+CMGS have a "> " prompt + writeCtrlZ(s_smsPDU); + s_smsPDU = NULL; + } else switch (s_type) { + case NO_RESULT: + handleUnsolicited(line); + break; + case NUMERIC: + if (sp_response->p_intermediates == NULL + && isdigit(line[0]) + ) { + addIntermediate(line); + } else { + /* either we already have an intermediate response or + the line doesn't begin with a digit */ + handleUnsolicited(line); + } + break; + case SINGLELINE: + if (sp_response->p_intermediates == NULL + && strStartsWith (line, s_responsePrefix) + ) { + addIntermediate(line); + } else { + /* we already have an intermediate response */ + handleUnsolicited(line); + } + break; + case MULTILINE: + if (strStartsWith (line, s_responsePrefix)) { + addIntermediate(line); + } else { + handleUnsolicited(line); + } + break; + + default: /* this should never be reached */ + LOGE("Unsupported AT command type %d\n", s_type); + handleUnsolicited(line); + break; + } + + pthread_mutex_unlock(&s_commandmutex); +} + + +/** + * Returns a pointer to the end of the next line + * special-cases the "> " SMS prompt + * + * returns NULL if there is no complete line + */ +static char * findNextEOL(char *cur) +{ + if (cur[0] == '>' && cur[1] == ' ' && cur[2] == '\0') { + /* SMS prompt character...not \r terminated */ + return cur+2; + } + + // Find next newline + while (*cur != '\0' && *cur != '\r' && *cur != '\n') cur++; + + return *cur == '\0' ? NULL : cur; +} + + +/** + * Reads a line from the AT channel, returns NULL on timeout. + * Assumes it has exclusive read access to the FD + * + * This line is valid only until the next call to readline + * + * This function exists because as of writing, android libc does not + * have buffered stdio. + */ + +static const char *readline() +{ + ssize_t count; + + char *p_read = NULL; + char *p_eol = NULL; + char *ret; + + /* this is a little odd. I use *s_ATBufferCur == 0 to + * mean "buffer consumed completely". If it points to a character, than + * the buffer continues until a \0 + */ + if (*s_ATBufferCur == '\0') { + /* empty buffer */ + s_ATBufferCur = s_ATBuffer; + *s_ATBufferCur = '\0'; + p_read = s_ATBuffer; + } else { /* *s_ATBufferCur != '\0' */ + /* there's data in the buffer from the last read */ + + // skip over leading newlines + while (*s_ATBufferCur == '\r' || *s_ATBufferCur == '\n') + s_ATBufferCur++; + + p_eol = findNextEOL(s_ATBufferCur); + + if (p_eol == NULL) { + /* a partial line. move it up and prepare to read more */ + size_t len; + + len = strlen(s_ATBufferCur); + + memmove(s_ATBuffer, s_ATBufferCur, len + 1); + p_read = s_ATBuffer + len; + s_ATBufferCur = s_ATBuffer; + } + /* Otherwise, (p_eol !- NULL) there is a complete line */ + /* that will be returned the while () loop below */ + } + + while (p_eol == NULL) { + if (0 == MAX_AT_RESPONSE - (p_read - s_ATBuffer)) { + LOGE("ERROR: Input line exceeded buffer\n"); + /* ditch buffer and start over again */ + s_ATBufferCur = s_ATBuffer; + *s_ATBufferCur = '\0'; + p_read = s_ATBuffer; + } + + do { + count = read(s_fd, p_read, + MAX_AT_RESPONSE - (p_read - s_ATBuffer)); + } while (count < 0 && errno == EINTR); + + if (count > 0) { + AT_DUMP( "<< ", p_read, count ); + s_readCount += count; + + p_read[count] = '\0'; + + // skip over leading newlines + while (*s_ATBufferCur == '\r' || *s_ATBufferCur == '\n') + s_ATBufferCur++; + + p_eol = findNextEOL(s_ATBufferCur); + p_read += count; + } else if (count <= 0) { + /* read error encountered or EOF reached */ + if(count == 0) { + LOGD("atchannel: EOF reached"); + } else { + LOGD("atchannel: read error %s", strerror(errno)); + } + return NULL; + } + } + + /* a full line in the buffer. Place a \0 over the \r and return */ + + ret = s_ATBufferCur; + *p_eol = '\0'; + s_ATBufferCur = p_eol + 1; /* this will always be <= p_read, */ + /* and there will be a \0 at *p_read */ + + LOGD("AT< %s\n", ret); + return ret; +} + + +static void onReaderClosed() +{ + if (s_onReaderClosed != NULL && s_readerClosed == 0) { + + pthread_mutex_lock(&s_commandmutex); + + s_readerClosed = 1; + + pthread_cond_signal(&s_commandcond); + + pthread_mutex_unlock(&s_commandmutex); + + s_onReaderClosed(); + } +} + + +static void *readerLoop(void *arg) +{ + for (;;) { + const char * line; + + line = readline(); + + if (line == NULL) { + break; + } + + if(isSMSUnsolicited(line)) { + char *line1; + const char *line2; + + // The scope of string returned by 'readline()' is valid only + // till next call to 'readline()' hence making a copy of line + // before calling readline again. + line1 = strdup(line); + line2 = readline(); + + if (line2 == NULL) { + break; + } + + if (s_unsolHandler != NULL) { + s_unsolHandler (line1, line2); + } + free(line1); + } else { + processLine(line); + } + +#ifdef HAVE_ANDROID_OS + if (s_ackPowerIoctl > 0) { + /* acknowledge that bytes have been read and processed */ + ioctl(s_fd, OMAP_CSMI_TTY_ACK, &s_readCount); + s_readCount = 0; + } +#endif /*HAVE_ANDROID_OS*/ + } + + onReaderClosed(); + + return NULL; +} + +/** + * Sends string s to the radio with a \r appended. + * Returns AT_ERROR_* on error, 0 on success + * + * This function exists because as of writing, android libc does not + * have buffered stdio. + */ +static int writeline (const char *s) +{ + size_t cur = 0; + size_t len = strlen(s); + ssize_t written; + + if (s_fd < 0 || s_readerClosed > 0) { + return AT_ERROR_CHANNEL_CLOSED; + } + + LOGD("AT> %s\n", s); + + AT_DUMP( ">> ", s, strlen(s) ); + + /* the main string */ + while (cur < len) { + do { + written = write (s_fd, s + cur, len - cur); + } while (written < 0 && errno == EINTR); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + cur += written; + } + + /* the \r */ + + do { + written = write (s_fd, "\r" , 1); + } while ((written < 0 && errno == EINTR) || (written == 0)); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + return 0; +} +static int writeCtrlZ (const char *s) +{ + size_t cur = 0; + size_t len = strlen(s); + ssize_t written; + + if (s_fd < 0 || s_readerClosed > 0) { + return AT_ERROR_CHANNEL_CLOSED; + } + + LOGD("AT> %s^Z\n", s); + + AT_DUMP( ">* ", s, strlen(s) ); + + /* the main string */ + while (cur < len) { + do { + written = write (s_fd, s + cur, len - cur); + } while (written < 0 && errno == EINTR); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + cur += written; + } + + /* the ^Z */ + + do { + written = write (s_fd, "\032" , 1); + } while ((written < 0 && errno == EINTR) || (written == 0)); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + return 0; +} + +static void clearPendingCommand() +{ + if (sp_response != NULL) { + at_response_free(sp_response); + } + + sp_response = NULL; + s_responsePrefix = NULL; + s_smsPDU = NULL; +} + + +/** + * Starts AT handler on stream "fd' + * returns 0 on success, -1 on error + */ +int at_open(int fd, ATUnsolHandler h) +{ + int ret; + pthread_t tid; + pthread_attr_t attr; + + s_fd = fd; + s_unsolHandler = h; + s_readerClosed = 0; + + s_responsePrefix = NULL; + s_smsPDU = NULL; + sp_response = NULL; + + /* Android power control ioctl */ +#ifdef HAVE_ANDROID_OS +#ifdef OMAP_CSMI_POWER_CONTROL + ret = ioctl(fd, OMAP_CSMI_TTY_ENABLE_ACK); + if(ret == 0) { + int ack_count; + int read_count; + int old_flags; + char sync_buf[256]; + old_flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, old_flags | O_NONBLOCK); + do { + ioctl(fd, OMAP_CSMI_TTY_READ_UNACKED, &ack_count); + read_count = 0; + do { + ret = read(fd, sync_buf, sizeof(sync_buf)); + if(ret > 0) + read_count += ret; + } while(ret > 0 || (ret < 0 && errno == EINTR)); + ioctl(fd, OMAP_CSMI_TTY_ACK, &ack_count); + } while(ack_count > 0 || read_count > 0); + fcntl(fd, F_SETFL, old_flags); + s_readCount = 0; + s_ackPowerIoctl = 1; + } + else + s_ackPowerIoctl = 0; + +#else // OMAP_CSMI_POWER_CONTROL + s_ackPowerIoctl = 0; + +#endif // OMAP_CSMI_POWER_CONTROL +#endif /*HAVE_ANDROID_OS*/ + + pthread_attr_init (&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr); + + if (ret < 0) { + perror ("pthread_create"); + return -1; + } + + + return 0; +} + +/* FIXME is it ok to call this from the reader and the command thread? */ +void at_close() +{ + if (s_fd >= 0) { + close(s_fd); + } + s_fd = -1; + + pthread_mutex_lock(&s_commandmutex); + + s_readerClosed = 1; + + pthread_cond_signal(&s_commandcond); + + pthread_mutex_unlock(&s_commandmutex); + + /* the reader thread should eventually die */ +} + +static ATResponse * at_response_new() +{ + return (ATResponse *) calloc(1, sizeof(ATResponse)); +} + +void at_response_free(ATResponse *p_response) +{ + ATLine *p_line; + + if (p_response == NULL) return; + + p_line = p_response->p_intermediates; + + while (p_line != NULL) { + ATLine *p_toFree; + + p_toFree = p_line; + p_line = p_line->p_next; + + free(p_toFree->line); + free(p_toFree); + } + + free (p_response->finalResponse); + free (p_response); +} + +/** + * The line reader places the intermediate responses in reverse order + * here we flip them back + */ +static void reverseIntermediates(ATResponse *p_response) +{ + ATLine *pcur,*pnext; + + pcur = p_response->p_intermediates; + p_response->p_intermediates = NULL; + + while (pcur != NULL) { + pnext = pcur->p_next; + pcur->p_next = p_response->p_intermediates; + p_response->p_intermediates = pcur; + pcur = pnext; + } +} + +/** + * Internal send_command implementation + * Doesn't lock or call the timeout callback + * + * timeoutMsec == 0 means infinite timeout + */ + +static int at_send_command_full_nolock (const char *command, ATCommandType type, + const char *responsePrefix, const char *smspdu, + long long timeoutMsec, ATResponse **pp_outResponse) +{ + int err = 0; +#ifndef USE_NP + struct timespec ts; +#endif /*USE_NP*/ + + if(sp_response != NULL) { + err = AT_ERROR_COMMAND_PENDING; + goto error; + } + + err = writeline (command); + + if (err < 0) { + goto error; + } + + s_type = type; + s_responsePrefix = responsePrefix; + s_smsPDU = smspdu; + sp_response = at_response_new(); + +#ifndef USE_NP + if (timeoutMsec != 0) { + setTimespecRelative(&ts, timeoutMsec); + } +#endif /*USE_NP*/ + + while (sp_response->finalResponse == NULL && s_readerClosed == 0) { + if (timeoutMsec != 0) { +#ifdef USE_NP + err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec); +#else + err = pthread_cond_timedwait(&s_commandcond, &s_commandmutex, &ts); +#endif /*USE_NP*/ + } else { + err = pthread_cond_wait(&s_commandcond, &s_commandmutex); + } + + if (err == ETIMEDOUT) { + err = AT_ERROR_TIMEOUT; + goto error; + } + } + + if (pp_outResponse == NULL) { + at_response_free(sp_response); + } else { + /* line reader stores intermediate responses in reverse order */ + reverseIntermediates(sp_response); + *pp_outResponse = sp_response; + } + + sp_response = NULL; + + if(s_readerClosed > 0) { + err = AT_ERROR_CHANNEL_CLOSED; + goto error; + } + + err = 0; +error: + clearPendingCommand(); + + return err; +} + +/** + * Internal send_command implementation + * + * timeoutMsec == 0 means infinite timeout + */ +static int at_send_command_full (const char *command, ATCommandType type, + const char *responsePrefix, const char *smspdu, + long long timeoutMsec, ATResponse **pp_outResponse) +{ + int err; + + if (0 != pthread_equal(s_tid_reader, pthread_self())) { + /* cannot be called from reader thread */ + return AT_ERROR_INVALID_THREAD; + } + + pthread_mutex_lock(&s_commandmutex); + + err = at_send_command_full_nolock(command, type, + responsePrefix, smspdu, + timeoutMsec, pp_outResponse); + + pthread_mutex_unlock(&s_commandmutex); + + if (err == AT_ERROR_TIMEOUT && s_onTimeout != NULL) { + s_onTimeout(); + } + + return err; +} + + +/** + * Issue a single normal AT command with no intermediate response expected + * + * "command" should not include \r + * pp_outResponse can be NULL + * + * if non-NULL, the resulting ATResponse * must be eventually freed with + * at_response_free + */ +int at_send_command (const char *command, ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, NO_RESULT, NULL, + NULL, 0, pp_outResponse); + + return err; +} + + +int at_send_command_singleline (const char *command, + const char *responsePrefix, + ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, SINGLELINE, responsePrefix, + NULL, 0, pp_outResponse); + + if (err == 0 && pp_outResponse != NULL + && (*pp_outResponse)->success > 0 + && (*pp_outResponse)->p_intermediates == NULL + ) { + /* successful command must have an intermediate response */ + at_response_free(*pp_outResponse); + *pp_outResponse = NULL; + return AT_ERROR_INVALID_RESPONSE; + } + + return err; +} + + +int at_send_command_numeric (const char *command, + ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, NUMERIC, NULL, + NULL, 0, pp_outResponse); + + if (err == 0 && pp_outResponse != NULL + && (*pp_outResponse)->success > 0 + && (*pp_outResponse)->p_intermediates == NULL + ) { + /* successful command must have an intermediate response */ + at_response_free(*pp_outResponse); + *pp_outResponse = NULL; + return AT_ERROR_INVALID_RESPONSE; + } + + return err; +} + + +int at_send_command_sms (const char *command, + const char *pdu, + const char *responsePrefix, + ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, SINGLELINE, responsePrefix, + pdu, 0, pp_outResponse); + + if (err == 0 && pp_outResponse != NULL + && (*pp_outResponse)->success > 0 + && (*pp_outResponse)->p_intermediates == NULL + ) { + /* successful command must have an intermediate response */ + at_response_free(*pp_outResponse); + *pp_outResponse = NULL; + return AT_ERROR_INVALID_RESPONSE; + } + + return err; +} + + +int at_send_command_multiline (const char *command, + const char *responsePrefix, + ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, MULTILINE, responsePrefix, + NULL, 0, pp_outResponse); + + return err; +} + + +/** This callback is invoked on the command thread */ +void at_set_on_timeout(void (*onTimeout)(void)) +{ + s_onTimeout = onTimeout; +} + +/** + * This callback is invoked on the reader thread (like ATUnsolHandler) + * when the input stream closes before you call at_close + * (not when you call at_close()) + * You should still call at_close() + */ + +void at_set_on_reader_closed(void (*onClose)(void)) +{ + s_onReaderClosed = onClose; +} + + +/** + * Periodically issue an AT command and wait for a response. + * Used to ensure channel has start up and is active + */ + +int at_handshake() +{ + int i; + int err = 0; + + if (0 != pthread_equal(s_tid_reader, pthread_self())) { + /* cannot be called from reader thread */ + return AT_ERROR_INVALID_THREAD; + } + + pthread_mutex_lock(&s_commandmutex); + + for (i = 0 ; i < HANDSHAKE_RETRY_COUNT ; i++) { + /* some stacks start with verbose off */ + err = at_send_command_full_nolock ("ATE0Q0V1", NO_RESULT, + NULL, NULL, HANDSHAKE_TIMEOUT_MSEC, NULL); + + if (err == 0) { + break; + } + } + + if (err == 0) { + /* pause for a bit to let the input buffer drain any unmatched OK's + (they will appear as extraneous unsolicited responses) */ + + sleepMsec(HANDSHAKE_TIMEOUT_MSEC); + } + + pthread_mutex_unlock(&s_commandmutex); + + return err; +} + +/** + * Returns error code from response + * Assumes AT+CMEE=1 (numeric) mode + */ +AT_CME_Error at_get_cme_error(const ATResponse *p_response) +{ + int ret; + int err; + char *p_cur; + + if (p_response->success > 0) { + return CME_SUCCESS; + } + + if (p_response->finalResponse == NULL + || !strStartsWith(p_response->finalResponse, "+CME ERROR:") + ) { + return CME_ERROR_NON_CME; + } + + p_cur = p_response->finalResponse; + err = at_tok_start(&p_cur); + + if (err < 0) { + return CME_ERROR_NON_CME; + } + + err = at_tok_nextint(&p_cur, &ret); + + if (err < 0) { + return CME_ERROR_NON_CME; + } + + return (AT_CME_Error) ret; +} + diff --git a/reference-ril/atchannel.h b/reference-ril/atchannel.h new file mode 100644 index 0000000..cfd49d5 --- /dev/null +++ b/reference-ril/atchannel.h @@ -0,0 +1,124 @@ +/* //device/system/reference-ril/atchannel.h +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef ATCHANNEL_H +#define ATCHANNEL_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* define AT_DEBUG to send AT traffic to /tmp/radio-at.log" */ +#define AT_DEBUG 0 + +#if AT_DEBUG +extern void AT_DUMP(const char* prefix, const char* buff, int len); +#else +#define AT_DUMP(prefix,buff,len) do{}while(0) +#endif + +#define AT_ERROR_GENERIC -1 +#define AT_ERROR_COMMAND_PENDING -2 +#define AT_ERROR_CHANNEL_CLOSED -3 +#define AT_ERROR_TIMEOUT -4 +#define AT_ERROR_INVALID_THREAD -5 /* AT commands may not be issued from + reader thread (or unsolicited response + callback */ +#define AT_ERROR_INVALID_RESPONSE -6 /* eg an at_send_command_singleline that + did not get back an intermediate + response */ + + +typedef enum { + NO_RESULT, /* no intermediate response expected */ + NUMERIC, /* a single intermediate response starting with a 0-9 */ + SINGLELINE, /* a single intermediate response starting with a prefix */ + MULTILINE /* multiple line intermediate response + starting with a prefix */ +} ATCommandType; + +/** a singly-lined list of intermediate responses */ +typedef struct ATLine { + struct ATLine *p_next; + char *line; +} ATLine; + +/** Free this with at_response_free() */ +typedef struct { + int success; /* true if final response indicates + success (eg "OK") */ + char *finalResponse; /* eg OK, ERROR */ + ATLine *p_intermediates; /* any intermediate responses */ +} ATResponse; + +/** + * a user-provided unsolicited response handler function + * this will be called from the reader thread, so do not block + * "s" is the line, and "sms_pdu" is either NULL or the PDU response + * for multi-line TS 27.005 SMS PDU responses (eg +CMT:) + */ +typedef void (*ATUnsolHandler)(const char *s, const char *sms_pdu); + +int at_open(int fd, ATUnsolHandler h); +void at_close(); + +/* This callback is invoked on the command thread. + You should reset or handshake here to avoid getting out of sync */ +void at_set_on_timeout(void (*onTimeout)(void)); +/* This callback is invoked on the reader thread (like ATUnsolHandler) + when the input stream closes before you call at_close + (not when you call at_close()) + You should still call at_close() + It may also be invoked immediately from the current thread if the read + channel is already closed */ +void at_set_on_reader_closed(void (*onClose)(void)); + +int at_send_command_singleline (const char *command, + const char *responsePrefix, + ATResponse **pp_outResponse); + +int at_send_command_numeric (const char *command, + ATResponse **pp_outResponse); + +int at_send_command_multiline (const char *command, + const char *responsePrefix, + ATResponse **pp_outResponse); + + +int at_handshake(); + +int at_send_command (const char *command, ATResponse **pp_outResponse); + +int at_send_command_sms (const char *command, const char *pdu, + const char *responsePrefix, + ATResponse **pp_outResponse); + +void at_response_free(ATResponse *p_response); + +typedef enum { + CME_ERROR_NON_CME = -1, + CME_SUCCESS = 0, + CME_SIM_NOT_INSERTED = 10 +} AT_CME_Error; + +AT_CME_Error at_get_cme_error(const ATResponse *p_response); + +#ifdef __cplusplus +} +#endif + +#endif /*ATCHANNEL_H*/ diff --git a/reference-ril/misc.c b/reference-ril/misc.c new file mode 100644 index 0000000..7945e75 --- /dev/null +++ b/reference-ril/misc.c @@ -0,0 +1,29 @@ +/* //device/system/reference-ril/misc.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +/** returns 1 if line starts with prefix, 0 if it does not */ +int strStartsWith(const char *line, const char *prefix) +{ + for ( ; *line != '\0' && *prefix != '\0' ; line++, prefix++) { + if (*line != *prefix) { + return 0; + } + } + + return *prefix == '\0'; +} + diff --git a/reference-ril/misc.h b/reference-ril/misc.h new file mode 100644 index 0000000..bafe7df --- /dev/null +++ b/reference-ril/misc.h @@ -0,0 +1,19 @@ +/* //device/system/reference-ril/misc.h +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +/** returns 1 if line starts with prefix, 0 if it does not */ +int strStartsWith(const char *line, const char *prefix); diff --git a/reference-ril/reference-ril.c b/reference-ril/reference-ril.c new file mode 100644 index 0000000..ccb5b62 --- /dev/null +++ b/reference-ril/reference-ril.c @@ -0,0 +1,2052 @@ +/* //device/system/reference-ril/reference-ril.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <telephony/ril.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pthread.h> +#include <alloca.h> +#include "atchannel.h" +#include "at_tok.h" +#include "misc.h" +#include <getopt.h> +#include <sys/socket.h> +#include <cutils/sockets.h> +#include <termios.h> + +#define LOG_TAG "RIL" +#include <utils/Log.h> + +#define MAX_AT_RESPONSE 0x1000 + +/* pathname returned from RIL_REQUEST_SETUP_DEFAULT_PDP */ +#define PPP_TTY_PATH "/dev/omap_csmi_tty1" + +#ifdef USE_TI_COMMANDS + +// Enable a workaround +// 1) Make incoming call, do not answer +// 2) Hangup remote end +// Expected: call should disappear from CLCC line +// Actual: Call shows as "ACTIVE" before disappearing +#define WORKAROUND_ERRONEOUS_ANSWER 1 + +// Some varients of the TI stack do not support the +CGEV unsolicited +// response. However, they seem to send an unsolicited +CME ERROR: 150 +#define WORKAROUND_FAKE_CGEV 1 +#endif + +static void onRequest (int request, void *data, size_t datalen, RIL_Token t); +static RIL_RadioState currentState(); +static int onSupports (int requestCode); +static void onCancel (RIL_Token t); +static const char *getVersion(); +static int isRadioOn(); +static int getSIMStatus(); +static void onPDPContextListChanged(void *param); + +extern const char * requestToString(int request); + +/*** Static Variables ***/ +static const RIL_RadioFunctions s_callbacks = { + RIL_VERSION, + onRequest, + currentState, + onSupports, + onCancel, + getVersion +}; + +#ifdef RIL_SHLIB +static const struct RIL_Env *s_rilenv; + +#define RIL_onRequestComplete(t, e, response, responselen) s_rilenv->OnRequestComplete(t,e, response, responselen) +#define RIL_onUnsolicitedResponse(a,b,c) s_rilenv->OnUnsolicitedResponse(a,b,c) +#define RIL_requestTimedCallback(a,b,c) s_rilenv->RequestTimedCallback(a,b,c) +#endif + +static RIL_RadioState sState = RADIO_STATE_UNAVAILABLE; + +static pthread_mutex_t s_state_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t s_state_cond = PTHREAD_COND_INITIALIZER; + +static int s_port = -1; +static const char * s_device_path = NULL; +static int s_device_socket = 0; + +/* trigger change to this with s_state_cond */ +static int s_closed = 0; + +static int sFD; /* file desc of AT channel */ +static char sATBuffer[MAX_AT_RESPONSE+1]; +static char *sATBufferCur = NULL; + +static const struct timeval TIMEVAL_SIMPOLL = {1,0}; +static const struct timeval TIMEVAL_CALLSTATEPOLL = {0,500000}; +static const struct timeval TIMEVAL_0 = {0,0}; + +#ifdef WORKAROUND_ERRONEOUS_ANSWER +// Max number of times we'll try to repoll when we think +// we have a AT+CLCC race condition +#define REPOLL_CALLS_COUNT_MAX 4 + +// Line index that was incoming or waiting at last poll, or -1 for none +static int s_incomingOrWaitingLine = -1; +// Number of times we've asked for a repoll of AT+CLCC +static int s_repollCallsCount = 0; +// Should we expect a call to be answered in the next CLCC? +static int s_expectAnswer = 0; +#endif /* WORKAROUND_ERRONEOUS_ANSWER */ + +static void pollSIMState (void *param); +static void setRadioState(RIL_RadioState newState); + +static int clccStateToRILState(int state, RIL_CallState *p_state) + +{ + switch(state) { + case 0: *p_state = RIL_CALL_ACTIVE; return 0; + case 1: *p_state = RIL_CALL_HOLDING; return 0; + case 2: *p_state = RIL_CALL_DIALING; return 0; + case 3: *p_state = RIL_CALL_ALERTING; return 0; + case 4: *p_state = RIL_CALL_INCOMING; return 0; + case 5: *p_state = RIL_CALL_WAITING; return 0; + default: return -1; + } +} + +/** + * Note: directly modified line and has *p_call point directly into + * modified line + */ +static int callFromCLCCLine(char *line, RIL_Call *p_call) +{ + //+CLCC: 1,0,2,0,0,\"+18005551212\",145 + // index,isMT,state,mode,isMpty(,number,TOA)? + + int err; + int state; + int mode; + + err = at_tok_start(&line); + if (err < 0) goto error; + + err = at_tok_nextint(&line, &(p_call->index)); + if (err < 0) goto error; + + err = at_tok_nextbool(&line, &(p_call->isMT)); + if (err < 0) goto error; + + err = at_tok_nextint(&line, &state); + if (err < 0) goto error; + + err = clccStateToRILState(state, &(p_call->state)); + if (err < 0) goto error; + + err = at_tok_nextint(&line, &mode); + if (err < 0) goto error; + + p_call->isVoice = (mode == 0); + + err = at_tok_nextbool(&line, &(p_call->isMpty)); + if (err < 0) goto error; + + if (at_tok_hasmore(&line)) { + err = at_tok_nextstr(&line, &(p_call->number)); + + /* tolerate null here */ + if (err < 0) return 0; + + // Some lame implementations return strings + // like "NOT AVAILABLE" in the CLCC line + if (p_call->number != NULL + && 0 == strspn(p_call->number, "+0123456789") + ) { + p_call->number = NULL; + } + + err = at_tok_nextint(&line, &p_call->toa); + if (err < 0) goto error; + } + + return 0; + +error: + LOGE("invalid CLCC line\n"); + return -1; +} + + +/** do post-AT+CFUN=1 initialization */ +static void onRadioPowerOn() +{ +#ifdef USE_TI_COMMANDS + /* Must be after CFUN=1 */ + /* TI specific -- notifications for CPHS things such */ + /* as CPHS message waiting indicator */ + + at_send_command("AT%CPHS=1", NULL); + + /* TI specific -- enable NITZ unsol notifs */ + at_send_command("AT%CTZV=1", NULL); +#endif + + pollSIMState(NULL); +} + +/** do post- SIM ready initialization */ +static void onSIMReady() +{ + at_send_command_singleline("AT+CSMS=1", "+CSMS:", NULL); + /* + * Always send SMS messages directly to the TE + * + * mode = 1 // discard when link is reserved (link should never be + * reserved) + * mt = 2 // most messages routed to TE + * bm = 2 // new cell BM's routed to TE + * ds = 1 // Status reports routed to TE + * bfr = 1 // flush buffer + */ + at_send_command("AT+CNMI=1,2,2,1,1", NULL); +} + +static void requestRadioPower(void *data, size_t datalen, RIL_Token t) +{ + int onOff; + + int err; + ATResponse *p_response = NULL; + + assert (datalen >= sizeof(int *)); + onOff = ((int *)data)[0]; + + if (onOff == 0 && sState != RADIO_STATE_OFF) { + err = at_send_command("AT+CFUN=0", &p_response); + if (err < 0 || p_response->success == 0) goto error; + setRadioState(RADIO_STATE_OFF); + } else if (onOff > 0 && sState == RADIO_STATE_OFF) { + err = at_send_command("AT+CFUN=1", &p_response); + if (err < 0|| p_response->success == 0) { + // Some stacks return an error when there is no SIM, + // but they really turn the RF portion on + // So, if we get an error, let's check to see if it + // turned on anyway + + if (isRadioOn() != 1) { + goto error; + } + } + setRadioState(RADIO_STATE_SIM_NOT_READY); + } + + at_response_free(p_response); + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + return; +error: + at_response_free(p_response); + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); +} + +static void requestOrSendPDPContextList(RIL_Token *t); + +static void onPDPContextListChanged(void *param) +{ + requestOrSendPDPContextList(NULL); +} + +static void requestPDPContextList(void *data, size_t datalen, RIL_Token t) +{ + requestOrSendPDPContextList(&t); +} + +static void requestOrSendPDPContextList(RIL_Token *t) +{ + ATResponse *p_response; + ATLine *p_cur; + int err; + int n = 0; + char *out; + + err = at_send_command_multiline ("AT+CGACT?", "+CGACT:", &p_response); + if (err != 0 || p_response->success == 0) { + if (t != NULL) + RIL_onRequestComplete(*t, RIL_E_GENERIC_FAILURE, NULL, 0); + else + RIL_onUnsolicitedResponse(RIL_UNSOL_PDP_CONTEXT_LIST_CHANGED, + NULL, 0); + return; + } + + for (p_cur = p_response->p_intermediates; p_cur != NULL; + p_cur = p_cur->p_next) + n++; + + RIL_PDP_Context_Response *responses = + alloca(n * sizeof(RIL_PDP_Context_Response)); + + int i; + for (i = 0; i < n; i++) { + responses[i].cid = -1; + responses[i].active = -1; + responses[i].type = ""; + responses[i].apn = ""; + responses[i].address = ""; + } + + RIL_PDP_Context_Response *response = responses; + for (p_cur = p_response->p_intermediates; p_cur != NULL; + p_cur = p_cur->p_next) { + char *line = p_cur->line; + + err = at_tok_start(&line); + if (err < 0) + goto error; + + err = at_tok_nextint(&line, &response->cid); + if (err < 0) + goto error; + + err = at_tok_nextint(&line, &response->active); + if (err < 0) + goto error; + + response++; + } + + at_response_free(p_response); + + err = at_send_command_multiline ("AT+CGDCONT?", "+CGDCONT:", &p_response); + if (err != 0 || p_response->success == 0) { + if (t != NULL) + RIL_onRequestComplete(*t, RIL_E_GENERIC_FAILURE, NULL, 0); + else + RIL_onUnsolicitedResponse(RIL_UNSOL_PDP_CONTEXT_LIST_CHANGED, + NULL, 0); + return; + } + + for (p_cur = p_response->p_intermediates; p_cur != NULL; + p_cur = p_cur->p_next) { + char *line = p_cur->line; + int cid; + char *type; + char *apn; + char *address; + + + err = at_tok_start(&line); + if (err < 0) + goto error; + + err = at_tok_nextint(&line, &cid); + if (err < 0) + goto error; + + for (i = 0; i < n; i++) { + if (responses[i].cid == cid) + break; + } + + if (i >= n) { + /* details for a context we didn't hear about in the last request */ + continue; + } + + err = at_tok_nextstr(&line, &out); + if (err < 0) + goto error; + + responses[i].type = alloca(strlen(out) + 1); + strcpy(responses[i].type, out); + + err = at_tok_nextstr(&line, &out); + if (err < 0) + goto error; + + responses[i].apn = alloca(strlen(out) + 1); + strcpy(responses[i].apn, out); + + err = at_tok_nextstr(&line, &out); + if (err < 0) + goto error; + + responses[i].address = alloca(strlen(out) + 1); + strcpy(responses[i].address, out); + } + + at_response_free(p_response); + + if (t != NULL) + RIL_onRequestComplete(*t, RIL_E_SUCCESS, responses, + n * sizeof(RIL_PDP_Context_Response)); + else + RIL_onUnsolicitedResponse(RIL_UNSOL_PDP_CONTEXT_LIST_CHANGED, + responses, + n * sizeof(RIL_PDP_Context_Response)); + + return; + +error: + if (t != NULL) + RIL_onRequestComplete(*t, RIL_E_GENERIC_FAILURE, NULL, 0); + else + RIL_onUnsolicitedResponse(RIL_UNSOL_PDP_CONTEXT_LIST_CHANGED, + NULL, 0); + + at_response_free(p_response); +} + +static void requestQueryNetworkSelectionMode( + void *data, size_t datalen, RIL_Token t) +{ + int err; + ATResponse *p_response = NULL; + int response = 0; + char *line; + + err = at_send_command_singleline("AT+COPS?", "+COPS:", &p_response); + + if (err < 0 || p_response->success == 0) { + goto error; + } + + line = p_response->p_intermediates->line; + + err = at_tok_start(&line); + + if (err < 0) { + goto error; + } + + err = at_tok_nextint(&line, &response); + + if (err < 0) { + goto error; + } + + RIL_onRequestComplete(t, RIL_E_SUCCESS, &response, sizeof(int)); + at_response_free(p_response); + return; +error: + at_response_free(p_response); + LOGE("requestQueryNetworkSelectionMode must never return error when radio is on"); + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); +} + +static void sendCallStateChanged(void *param) +{ + RIL_onUnsolicitedResponse ( + RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, + NULL, 0); +} + +static void requestGetCurrentCalls(void *data, size_t datalen, RIL_Token t) +{ + int err; + ATResponse *p_response; + ATLine *p_cur; + int countCalls; + int countValidCalls; + RIL_Call *p_calls; + RIL_Call **pp_calls; + int i; + int needRepoll = 0; + +#ifdef WORKAROUND_ERRONEOUS_ANSWER + int prevIncomingOrWaitingLine; + + prevIncomingOrWaitingLine = s_incomingOrWaitingLine; + s_incomingOrWaitingLine = -1; +#endif /*WORKAROUND_ERRONEOUS_ANSWER*/ + + err = at_send_command_multiline ("AT+CLCC", "+CLCC:", &p_response); + + if (err != 0 || p_response->success == 0) { + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + return; + } + + /* count the calls */ + for (countCalls = 0, p_cur = p_response->p_intermediates + ; p_cur != NULL + ; p_cur = p_cur->p_next + ) { + countCalls++; + } + + /* yes, there's an array of pointers and then an array of structures */ + + pp_calls = (RIL_Call **)alloca(countCalls * sizeof(RIL_Call *)); + p_calls = (RIL_Call *)alloca(countCalls * sizeof(RIL_Call)); + memset (p_calls, 0, countCalls * sizeof(RIL_Call)); + + /* init the pointer array */ + for(i = 0; i < countCalls ; i++) { + pp_calls[i] = &(p_calls[i]); + } + + for (countValidCalls = 0, p_cur = p_response->p_intermediates + ; p_cur != NULL + ; p_cur = p_cur->p_next + ) { + err = callFromCLCCLine(p_cur->line, p_calls + countValidCalls); + + if (err != 0) { + continue; + } + +#ifdef WORKAROUND_ERRONEOUS_ANSWER + if (p_calls[countValidCalls].state == RIL_CALL_INCOMING + || p_calls[countValidCalls].state == RIL_CALL_WAITING + ) { + s_incomingOrWaitingLine = p_calls[countValidCalls].index; + } +#endif /*WORKAROUND_ERRONEOUS_ANSWER*/ + + if (p_calls[countValidCalls].state != RIL_CALL_ACTIVE + && p_calls[countValidCalls].state != RIL_CALL_HOLDING + ) { + needRepoll = 1; + } + + countValidCalls++; + } + +#ifdef WORKAROUND_ERRONEOUS_ANSWER + // Basically: + // A call was incoming or waiting + // Now it's marked as active + // But we never answered it + // + // This is probably a bug, and the call will probably + // disappear from the call list in the next poll + if (prevIncomingOrWaitingLine >= 0 + && s_incomingOrWaitingLine < 0 + && s_expectAnswer == 0 + ) { + for (i = 0; i < countValidCalls ; i++) { + + if (p_calls[i].index == prevIncomingOrWaitingLine + && p_calls[i].state == RIL_CALL_ACTIVE + && s_repollCallsCount < REPOLL_CALLS_COUNT_MAX + ) { + LOGI( + "Hit WORKAROUND_ERRONOUS_ANSWER case." + " Repoll count: %d\n", s_repollCallsCount); + s_repollCallsCount++; + goto error; + } + } + } + + s_expectAnswer = 0; + s_repollCallsCount = 0; +#endif /*WORKAROUND_ERRONEOUS_ANSWER*/ + + RIL_onRequestComplete(t, RIL_E_SUCCESS, pp_calls, + countValidCalls * sizeof (RIL_Call *)); + + at_response_free(p_response); + +#ifdef POLL_CALL_STATE + if (countValidCalls) { // We don't seem to get a "NO CARRIER" message from + // smd, so we're forced to poll until the call ends. +#else + if (needRepoll) { +#endif + RIL_requestTimedCallback (sendCallStateChanged, NULL, &TIMEVAL_CALLSTATEPOLL); + } + + return; +error: + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + at_response_free(p_response); +} + +static void requestDial(void *data, size_t datalen, RIL_Token t) +{ + RIL_Dial *p_dial; + char *cmd; + const char *clir; + int ret; + + p_dial = (RIL_Dial *)data; + + switch (p_dial->clir) { + case 1: clir = "I"; break; /*invocation*/ + case 2: clir = "i"; break; /*suppression*/ + default: + case 0: clir = ""; break; /*subscription default*/ + } + + asprintf(&cmd, "ATD%s%s;", p_dial->address, clir); + + ret = at_send_command(cmd, NULL); + + free(cmd); + + /* success or failure is ignored by the upper layer here. + it will call GET_CURRENT_CALLS and determine success that way */ + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); +} + +static void requestWriteSmsToSim(void *data, size_t datalen, RIL_Token t) +{ + RIL_SMS_WriteArgs *p_args; + char *cmd; + int length; + int err; + ATResponse *p_response = NULL; + + p_args = (RIL_SMS_WriteArgs *)data; + + length = strlen(p_args->pdu)/2; + asprintf(&cmd, "AT+CMGW=%d,%d", length, p_args->status); + + err = at_send_command_sms(cmd, p_args->pdu, "+CMGW:", &p_response); + + if (err != 0 || p_response->success == 0) goto error; + + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + at_response_free(p_response); + + return; +error: + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + at_response_free(p_response); +} + +static void requestHangup(void *data, size_t datalen, RIL_Token t) +{ + int *p_line; + + int ret; + char *cmd; + + p_line = (int *)data; + + // 3GPP 22.030 6.5.5 + // "Releases a specific active call X" + asprintf(&cmd, "AT+CHLD=1%d", p_line[0]); + + ret = at_send_command(cmd, NULL); + + free(cmd); + + /* success or failure is ignored by the upper layer here. + it will call GET_CURRENT_CALLS and determine success that way */ + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); +} + +static void requestSignalStrength(void *data, size_t datalen, RIL_Token t) +{ + ATResponse *p_response = NULL; + int err; + int response[2]; + char *line; + + err = at_send_command_singleline("AT+CSQ", "+CSQ:", &p_response); + + if (err < 0 || p_response->success == 0) { + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + goto error; + } + + line = p_response->p_intermediates->line; + + err = at_tok_start(&line); + if (err < 0) goto error; + + err = at_tok_nextint(&line, &(response[0])); + if (err < 0) goto error; + + err = at_tok_nextint(&line, &(response[1])); + if (err < 0) goto error; + + RIL_onRequestComplete(t, RIL_E_SUCCESS, response, sizeof(response)); + + at_response_free(p_response); + return; + +error: + LOGE("requestSignalStrength must never return an error when radio is on"); + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + at_response_free(p_response); +} + +static void requestRegistrationState(int request, void *data, + size_t datalen, RIL_Token t) +{ + int err; + int response[4]; + char * responseStr[4]; + ATResponse *p_response = NULL; + const char *cmd; + const char *prefix; + char *line, *p; + int commas; + int skip; + int count = 3; + + + if (request == RIL_REQUEST_REGISTRATION_STATE) { + cmd = "AT+CREG?"; + prefix = "+CREG:"; + } else if (request == RIL_REQUEST_GPRS_REGISTRATION_STATE) { + cmd = "AT+CGREG?"; + prefix = "+CGREG:"; + } else { + assert(0); + goto error; + } + + err = at_send_command_singleline(cmd, prefix, &p_response); + + if (err != 0) goto error; + + line = p_response->p_intermediates->line; + + err = at_tok_start(&line); + if (err < 0) goto error; + + /* Ok you have to be careful here + * The solicited version of the CREG response is + * +CREG: n, stat, [lac, cid] + * and the unsolicited version is + * +CREG: stat, [lac, cid] + * The <n> parameter is basically "is unsolicited creg on?" + * which it should always be + * + * Now we should normally get the solicited version here, + * but the unsolicited version could have snuck in + * so we have to handle both + * + * Also since the LAC and CID are only reported when registered, + * we can have 1, 2, 3, or 4 arguments here + * + * finally, a +CGREG: answer may have a fifth value that corresponds + * to the network type, as in; + * + * +CGREG: n, stat [,lac, cid [,networkType]] + */ + + /* count number of commas */ + commas = 0; + for (p = line ; *p != '\0' ;p++) { + if (*p == ',') commas++; + } + + switch (commas) { + case 0: /* +CREG: <stat> */ + err = at_tok_nextint(&line, &response[0]); + if (err < 0) goto error; + response[1] = -1; + response[2] = -1; + break; + + case 1: /* +CREG: <n>, <stat> */ + err = at_tok_nextint(&line, &skip); + if (err < 0) goto error; + err = at_tok_nextint(&line, &response[0]); + if (err < 0) goto error; + response[1] = -1; + response[2] = -1; + if (err < 0) goto error; + break; + + case 2: /* +CREG: <stat>, <lac>, <cid> */ + err = at_tok_nextint(&line, &response[0]); + if (err < 0) goto error; + err = at_tok_nexthexint(&line, &response[1]); + if (err < 0) goto error; + err = at_tok_nexthexint(&line, &response[2]); + if (err < 0) goto error; + break; + case 3: /* +CREG: <n>, <stat>, <lac>, <cid> */ + err = at_tok_nextint(&line, &skip); + if (err < 0) goto error; + err = at_tok_nextint(&line, &response[0]); + if (err < 0) goto error; + err = at_tok_nexthexint(&line, &response[1]); + if (err < 0) goto error; + err = at_tok_nexthexint(&line, &response[2]); + if (err < 0) goto error; + break; + /* special case for CGREG, there is a fourth parameter + * that is the network type (unknown/gprs/edge/umts) + */ + case 4: /* +CGREG: <n>, <stat>, <lac>, <cid>, <networkType> */ + err = at_tok_nextint(&line, &skip); + if (err < 0) goto error; + err = at_tok_nextint(&line, &response[0]); + if (err < 0) goto error; + err = at_tok_nexthexint(&line, &response[1]); + if (err < 0) goto error; + err = at_tok_nexthexint(&line, &response[2]); + if (err < 0) goto error; + err = at_tok_nexthexint(&line, &response[3]); + if (err < 0) goto error; + count = 4; + break; + default: + goto error; + } + + asprintf(&responseStr[0], "%d", response[0]); + asprintf(&responseStr[1], "%d", response[1]); + asprintf(&responseStr[2], "%d", response[2]); + + if (count > 3) + asprintf(&responseStr[3], "%d", response[3]); + + RIL_onRequestComplete(t, RIL_E_SUCCESS, responseStr, count*sizeof(char*)); + at_response_free(p_response); + + return; +error: + LOGE("requestRegistrationState must never return an error when radio is on"); + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + at_response_free(p_response); +} + +static void requestOperator(void *data, size_t datalen, RIL_Token t) +{ + int err; + int i; + int skip; + ATLine *p_cur; + char *response[3]; + + memset(response, 0, sizeof(response)); + + ATResponse *p_response = NULL; + + err = at_send_command_multiline( + "AT+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?", + "+COPS:", &p_response); + + /* we expect 3 lines here: + * +COPS: 0,0,"T - Mobile" + * +COPS: 0,1,"TMO" + * +COPS: 0,2,"310170" + */ + + if (err != 0) goto error; + + for (i = 0, p_cur = p_response->p_intermediates + ; p_cur != NULL + ; p_cur = p_cur->p_next, i++ + ) { + char *line = p_cur->line; + + err = at_tok_start(&line); + if (err < 0) goto error; + + err = at_tok_nextint(&line, &skip); + if (err < 0) goto error; + + // If we're unregistered, we may just get + // a "+COPS: 0" response + if (!at_tok_hasmore(&line)) { + response[i] = NULL; + continue; + } + + err = at_tok_nextint(&line, &skip); + if (err < 0) goto error; + + // a "+COPS: 0, n" response is also possible + if (!at_tok_hasmore(&line)) { + response[i] = NULL; + continue; + } + + err = at_tok_nextstr(&line, &(response[i])); + if (err < 0) goto error; + } + + if (i != 3) { + /* expect 3 lines exactly */ + goto error; + } + + RIL_onRequestComplete(t, RIL_E_SUCCESS, response, sizeof(response)); + at_response_free(p_response); + + return; +error: + LOGE("requestOperator must not return error when radio is on"); + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + at_response_free(p_response); +} + +static void requestSendSMS(void *data, size_t datalen, RIL_Token t) +{ + int err; + const char *smsc; + const char *pdu; + int tpLayerLength; + char *cmd1, *cmd2; + RIL_SMS_Response response; + ATResponse *p_response = NULL; + + smsc = ((const char **)data)[0]; + pdu = ((const char **)data)[1]; + + tpLayerLength = strlen(pdu)/2; + + // "NULL for default SMSC" + if (smsc == NULL) { + smsc= "00"; + } + + asprintf(&cmd1, "AT+CMGS=%d", tpLayerLength); + asprintf(&cmd2, "%s%s", smsc, pdu); + + err = at_send_command_sms(cmd1, cmd2, "+CMGS:", &p_response); + + if (err != 0 || p_response->success == 0) goto error; + + memset(&response, 0, sizeof(response)); + + /* FIXME fill in messageRef and ackPDU */ + + RIL_onRequestComplete(t, RIL_E_SUCCESS, &response, sizeof(response)); + at_response_free(p_response); + + return; +error: + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + at_response_free(p_response); +} + +static void requestSetupDefaultPDP(void *data, size_t datalen, RIL_Token t) +{ + const char *apn; + char *cmd; + int err; + ATResponse *p_response = NULL; + char *response[2] = { "1", PPP_TTY_PATH }; + + apn = ((const char **)data)[0]; + +#ifdef USE_TI_COMMANDS + // Config for multislot class 10 (probably default anyway eh?) + err = at_send_command("AT%CPRIM=\"GMM\",\"CONFIG MULTISLOT_CLASS=<10>\"", + NULL); + + err = at_send_command("AT%DATA=2,\"UART\",1,,\"SER\",\"UART\",0", NULL); +#endif /* USE_TI_COMMANDS */ + + int fd, qmistatus; + size_t cur = 0; + size_t len; + ssize_t written, rlen; + char status[32] = {0}; + int retry = 10; + + LOGD("requesting data connection to APN '%s'", apn); + + fd = open ("/dev/qmi", O_RDWR); + if (fd >= 0) { /* the device doesn't exist on the emulator */ + + LOGD("opened the qmi device\n"); + asprintf(&cmd, "up:%s", apn); + len = strlen(cmd); + + while (cur < len) { + do { + written = write (fd, cmd + cur, len - cur); + } while (written < 0 && errno == EINTR); + + if (written < 0) { + LOGE("### ERROR writing to /dev/qmi"); + close(fd); + goto error; + } + + cur += written; + } + + // wait for interface to come online + + do { + sleep(1); + do { + rlen = read(fd, status, 31); + } while (rlen < 0 && errno == EINTR); + + if (rlen < 0) { + LOGE("### ERROR reading from /dev/qmi"); + close(fd); + goto error; + } else { + status[rlen] = '\0'; + LOGD("### status: %s", status); + } + } while (strncmp(status, "STATE=up", 8) && strcmp(status, "online") && --retry); + + close(fd); + + if (retry == 0) { + LOGE("### Failed to get data connection up\n"); + goto error; + } + + qmistatus = system("netcfg rmnet0 dhcp"); + + LOGD("netcfg rmnet0 dhcp: status %d\n", qmistatus); + + if (qmistatus < 0) goto error; + + } else { + + asprintf(&cmd, "AT+CGDCONT=1,\"IP\",\"%s\",,0,0", apn); + //FIXME check for error here + err = at_send_command(cmd, NULL); + free(cmd); + + // Set required QoS params to default + err = at_send_command("AT+CGQREQ=1", NULL); + + // Set minimum QoS params to default + err = at_send_command("AT+CGQMIN=1", NULL); + + // packet-domain event reporting + err = at_send_command("AT+CGEREP=1,0", NULL); + + // Hangup anything that's happening there now + err = at_send_command("AT+CGACT=1,0", NULL); + + // Start data on PDP context 1 + err = at_send_command("ATD*99***1#", &p_response); + + if (err < 0 || p_response->success == 0) { + goto error; + } + } + + RIL_onRequestComplete(t, RIL_E_SUCCESS, response, sizeof(response)); + at_response_free(p_response); + + return; +error: + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + at_response_free(p_response); + +} + +static void requestSMSAcknowledge(void *data, size_t datalen, RIL_Token t) +{ + int ackSuccess; + int err; + + ackSuccess = ((int *)data)[0]; + + if (ackSuccess == 1) { + err = at_send_command("AT+CNMA=1", NULL); + } else if (ackSuccess == 0) { + err = at_send_command("AT+CNMA=2", NULL); + } else { + LOGE("unsupported arg to RIL_REQUEST_SMS_ACKNOWLEDGE\n"); + goto error; + } + + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); +error: + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + +} + +static void requestSIM_IO(void *data, size_t datalen, RIL_Token t) +{ + ATResponse *p_response = NULL; + RIL_SIM_IO_Response sr; + int err; + char *cmd = NULL; + RIL_SIM_IO *p_args; + char *line; + + memset(&sr, 0, sizeof(sr)); + + p_args = (RIL_SIM_IO *)data; + + /* FIXME handle pin2 */ + + if (p_args->data == NULL) { + asprintf(&cmd, "AT+CRSM=%d,%d,%d,%d,%d", + p_args->command, p_args->fileid, + p_args->p1, p_args->p2, p_args->p3); + } else { + asprintf(&cmd, "AT+CRSM=%d,%d,%d,%d,%d,%s", + p_args->command, p_args->fileid, + p_args->p1, p_args->p2, p_args->p3, p_args->data); + } + + err = at_send_command_singleline(cmd, "+CRSM:", &p_response); + + if (err < 0 || p_response->success == 0) { + goto error; + } + + line = p_response->p_intermediates->line; + + err = at_tok_start(&line); + if (err < 0) goto error; + + err = at_tok_nextint(&line, &(sr.sw1)); + if (err < 0) goto error; + + err = at_tok_nextint(&line, &(sr.sw2)); + if (err < 0) goto error; + + if (at_tok_hasmore(&line)) { + err = at_tok_nextstr(&line, &(sr.simResponse)); + if (err < 0) goto error; + } + + RIL_onRequestComplete(t, RIL_E_SUCCESS, &sr, sizeof(sr)); + at_response_free(p_response); + free(cmd); + + return; +error: + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + at_response_free(p_response); + free(cmd); + +} + +static void requestEnterSimPin(void* data, size_t datalen, RIL_Token t) +{ + ATResponse *p_response = NULL; + int err; + char* cmd = NULL; + const char** strings = (const char**)data;; + + if ( datalen == sizeof(char*) ) { + asprintf(&cmd, "AT+CPIN=%s", strings[0]); + } else if ( datalen == 2*sizeof(char*) ) { + asprintf(&cmd, "AT+CPIN=%s,%s", strings[0], strings[1]); + } else + goto error; + + err = at_send_command_singleline(cmd, "+CPIN:", &p_response); + free(cmd); + + if (err < 0 || p_response->success == 0) { +error: + RIL_onRequestComplete(t, RIL_E_PASSWORD_INCORRECT, NULL, 0); + } else { + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + } + at_response_free(p_response); +} + + +static void requestSendUSSD(void *data, size_t datalen, RIL_Token t) +{ + const char *ussdRequest; + + ussdRequest = (char *)(data); + + + RIL_onRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0); + +// @@@ TODO + +} + + +/*** Callback methods from the RIL library to us ***/ + +/** + * Call from RIL to us to make a RIL_REQUEST + * + * Must be completed with a call to RIL_onRequestComplete() + * + * RIL_onRequestComplete() may be called from any thread, before or after + * this function returns. + * + * Will always be called from the same thread, so returning here implies + * that the radio is ready to process another command (whether or not + * the previous command has completed). + */ +static void +onRequest (int request, void *data, size_t datalen, RIL_Token t) +{ + ATResponse *p_response; + int err; + + LOGD("onRequest: %s", requestToString(request)); + + /* Ignore all requests except RIL_REQUEST_GET_SIM_STATUS + * when RADIO_STATE_UNAVAILABLE. + */ + if (sState == RADIO_STATE_UNAVAILABLE + && request != RIL_REQUEST_GET_SIM_STATUS + ) { + RIL_onRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0); + return; + } + + /* Ignore all non-power requests when RADIO_STATE_OFF + * (except RIL_REQUEST_GET_SIM_STATUS) + */ + if (sState == RADIO_STATE_OFF + && !(request == RIL_REQUEST_RADIO_POWER + || request == RIL_REQUEST_GET_SIM_STATUS) + ) { + RIL_onRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0); + return; + } + + switch (request) { + case RIL_REQUEST_GET_SIM_STATUS: { + int simStatus; + + simStatus = getSIMStatus(); + + RIL_onRequestComplete(t, RIL_E_SUCCESS, &simStatus, sizeof(simStatus)); + break; + } + case RIL_REQUEST_GET_CURRENT_CALLS: + requestGetCurrentCalls(data, datalen, t); + break; + case RIL_REQUEST_DIAL: + requestDial(data, datalen, t); + break; + case RIL_REQUEST_HANGUP: + requestHangup(data, datalen, t); + break; + case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND: + // 3GPP 22.030 6.5.5 + // "Releases all held calls or sets User Determined User Busy + // (UDUB) for a waiting call." + at_send_command("AT+CHLD=0", NULL); + + /* success or failure is ignored by the upper layer here. + it will call GET_CURRENT_CALLS and determine success that way */ + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + break; + case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND: + // 3GPP 22.030 6.5.5 + // "Releases all active calls (if any exist) and accepts + // the other (held or waiting) call." + at_send_command("AT+CHLD=1", NULL); + + /* success or failure is ignored by the upper layer here. + it will call GET_CURRENT_CALLS and determine success that way */ + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + break; + case RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE: + // 3GPP 22.030 6.5.5 + // "Places all active calls (if any exist) on hold and accepts + // the other (held or waiting) call." + at_send_command("AT+CHLD=2", NULL); + +#ifdef WORKAROUND_ERRONEOUS_ANSWER + s_expectAnswer = 1; +#endif /* WORKAROUND_ERRONEOUS_ANSWER */ + + /* success or failure is ignored by the upper layer here. + it will call GET_CURRENT_CALLS and determine success that way */ + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + break; + case RIL_REQUEST_ANSWER: + at_send_command("ATA", NULL); + +#ifdef WORKAROUND_ERRONEOUS_ANSWER + s_expectAnswer = 1; +#endif /* WORKAROUND_ERRONEOUS_ANSWER */ + + /* success or failure is ignored by the upper layer here. + it will call GET_CURRENT_CALLS and determine success that way */ + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + break; + case RIL_REQUEST_CONFERENCE: + // 3GPP 22.030 6.5.5 + // "Adds a held call to the conversation" + at_send_command("AT+CHLD=3", NULL); + + /* success or failure is ignored by the upper layer here. + it will call GET_CURRENT_CALLS and determine success that way */ + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + break; + case RIL_REQUEST_UDUB: + /* user determined user busy */ + /* sometimes used: ATH */ + at_send_command("ATH", NULL); + + /* success or failure is ignored by the upper layer here. + it will call GET_CURRENT_CALLS and determine success that way */ + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + break; + + case RIL_REQUEST_SEPARATE_CONNECTION: + { + char cmd[12]; + int party = ((int*)data)[0]; + + // Make sure that party is in a valid range. + // (Note: The Telephony middle layer imposes a range of 1 to 7. + // It's sufficient for us to just make sure it's single digit.) + if (party > 0 && party < 10) { + sprintf(cmd, "AT+CHLD=2%d", party); + at_send_command(cmd, NULL); + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + } else { + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + } + } + break; + + case RIL_REQUEST_SIGNAL_STRENGTH: + requestSignalStrength(data, datalen, t); + break; + case RIL_REQUEST_REGISTRATION_STATE: + case RIL_REQUEST_GPRS_REGISTRATION_STATE: + requestRegistrationState(request, data, datalen, t); + break; + case RIL_REQUEST_OPERATOR: + requestOperator(data, datalen, t); + break; + case RIL_REQUEST_RADIO_POWER: + requestRadioPower(data, datalen, t); + break; + case RIL_REQUEST_DTMF: { + char c = ((char *)data)[0]; + char *cmd; + asprintf(&cmd, "AT+VTS=%c", (int)c); + at_send_command(cmd, NULL); + free(cmd); + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + break; + } + case RIL_REQUEST_SEND_SMS: + requestSendSMS(data, datalen, t); + break; + case RIL_REQUEST_SETUP_DEFAULT_PDP: + requestSetupDefaultPDP(data, datalen, t); + break; + case RIL_REQUEST_SMS_ACKNOWLEDGE: + requestSMSAcknowledge(data, datalen, t); + break; + + case RIL_REQUEST_GET_IMSI: + p_response = NULL; + err = at_send_command_numeric("AT+CIMI", &p_response); + + if (err < 0 || p_response->success == 0) { + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + } else { + RIL_onRequestComplete(t, RIL_E_SUCCESS, + p_response->p_intermediates->line, sizeof(char *)); + } + at_response_free(p_response); + break; + + case RIL_REQUEST_GET_IMEI: + p_response = NULL; + err = at_send_command_numeric("AT+CGSN", &p_response); + + if (err < 0 || p_response->success == 0) { + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + } else { + RIL_onRequestComplete(t, RIL_E_SUCCESS, + p_response->p_intermediates->line, sizeof(char *)); + } + at_response_free(p_response); + break; + + case RIL_REQUEST_SIM_IO: + requestSIM_IO(data,datalen,t); + break; + + case RIL_REQUEST_SEND_USSD: + requestSendUSSD(data, datalen, t); + break; + + case RIL_REQUEST_CANCEL_USSD: + p_response = NULL; + err = at_send_command_numeric("AT+CUSD=2", &p_response); + + if (err < 0 || p_response->success == 0) { + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + } else { + RIL_onRequestComplete(t, RIL_E_SUCCESS, + p_response->p_intermediates->line, sizeof(char *)); + } + at_response_free(p_response); + break; + + case RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC: + at_send_command("AT+COPS=0", NULL); + break; + + case RIL_REQUEST_PDP_CONTEXT_LIST: + requestPDPContextList(data, datalen, t); + break; + + case RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE: + requestQueryNetworkSelectionMode(data, datalen, t); + break; + + case RIL_REQUEST_OEM_HOOK_RAW: + // echo back data + RIL_onRequestComplete(t, RIL_E_SUCCESS, data, datalen); + break; + + + case RIL_REQUEST_OEM_HOOK_STRINGS: { + int i; + const char ** cur; + + LOGD("got OEM_HOOK_STRINGS: 0x%8p %lu", data, (long)datalen); + + + for (i = (datalen / sizeof (char *)), cur = (const char **)data ; + i > 0 ; cur++, i --) { + LOGD("> '%s'", *cur); + } + + // echo back strings + RIL_onRequestComplete(t, RIL_E_SUCCESS, data, datalen); + break; + } + + case RIL_REQUEST_WRITE_SMS_TO_SIM: + requestWriteSmsToSim(data, datalen, t); + break; + + case RIL_REQUEST_DELETE_SMS_ON_SIM: { + char * cmd; + p_response = NULL; + asprintf(&cmd, "AT+CMGD=%d", ((int *)data)[0]); + err = at_send_command(cmd, &p_response); + free(cmd); + if (err < 0 || p_response->success == 0) { + RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0); + } else { + RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); + } + at_response_free(p_response); + break; + } + + case RIL_REQUEST_ENTER_SIM_PIN: + case RIL_REQUEST_ENTER_SIM_PUK: + case RIL_REQUEST_ENTER_SIM_PIN2: + case RIL_REQUEST_ENTER_SIM_PUK2: + case RIL_REQUEST_CHANGE_SIM_PIN: + case RIL_REQUEST_CHANGE_SIM_PIN2: + requestEnterSimPin(data, datalen, t); + break; + + default: + RIL_onRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0); + break; + } +} + +/** + * Synchronous call from the RIL to us to return current radio state. + * RADIO_STATE_UNAVAILABLE should be the initial state. + */ +static RIL_RadioState +currentState() +{ + return sState; +} +/** + * Call from RIL to us to find out whether a specific request code + * is supported by this implementation. + * + * Return 1 for "supported" and 0 for "unsupported" + */ + +static int +onSupports (int requestCode) +{ + //@@@ todo + + return 1; +} + +static void onCancel (RIL_Token t) +{ + //@@@todo + +} + +static const char * getVersion(void) +{ + return "android reference-ril 1.0"; +} + +static void +setRadioState(RIL_RadioState newState) +{ + RIL_RadioState oldState; + + pthread_mutex_lock(&s_state_mutex); + + oldState = sState; + + if (s_closed > 0) { + // If we're closed, the only reasonable state is + // RADIO_STATE_UNAVAILABLE + // This is here because things on the main thread + // may attempt to change the radio state after the closed + // event happened in another thread + newState = RADIO_STATE_UNAVAILABLE; + } + + if (sState != newState || s_closed > 0) { + sState = newState; + + pthread_cond_broadcast (&s_state_cond); + } + + pthread_mutex_unlock(&s_state_mutex); + + + /* do these outside of the mutex */ + if (sState != oldState) { + RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, + NULL, 0); + + /* FIXME onSimReady() and onRadioPowerOn() cannot be called + * from the AT reader thread + * Currently, this doesn't happen, but if that changes then these + * will need to be dispatched on the request thread + */ + if (sState == RADIO_STATE_SIM_READY) { + onSIMReady(); + } else if (sState == RADIO_STATE_SIM_NOT_READY) { + onRadioPowerOn(); + } + } +} + +/** returns one of RIM_SIM_*. Returns RIL_SIM_NOT_READY on error */ +static int +getSIMStatus() +{ + ATResponse *p_response = NULL; + int err; + int ret; + char *cpinLine; + char *cpinResult; + + if (sState == RADIO_STATE_OFF || sState == RADIO_STATE_UNAVAILABLE) { + ret = RIL_SIM_NOT_READY; + goto done; + } + + err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response); + + if (err != 0) { + ret = RIL_SIM_NOT_READY; + goto done; + } + + switch (at_get_cme_error(p_response)) { + case CME_SUCCESS: + break; + + case CME_SIM_NOT_INSERTED: + ret = RIL_SIM_ABSENT; + goto done; + + default: + ret = RIL_SIM_NOT_READY; + goto done; + } + + /* CPIN? has succeeded, now look at the result */ + + cpinLine = p_response->p_intermediates->line; + err = at_tok_start (&cpinLine); + + if (err < 0) { + ret = RIL_SIM_NOT_READY; + goto done; + } + + err = at_tok_nextstr(&cpinLine, &cpinResult); + + if (err < 0) { + ret = RIL_SIM_NOT_READY; + goto done; + } + + if (0 == strcmp (cpinResult, "SIM PIN")) { + ret = RIL_SIM_PIN; + goto done; + } else if (0 == strcmp (cpinResult, "SIM PUK")) { + ret = RIL_SIM_PUK; + goto done; + } else if (0 == strcmp (cpinResult, "PH-NET PIN")) { + return RIL_SIM_NETWORK_PERSONALIZATION; + } else if (0 != strcmp (cpinResult, "READY")) { + /* we're treating unsupported lock types as "sim absent" */ + ret = RIL_SIM_ABSENT; + goto done; + } + + at_response_free(p_response); + p_response = NULL; + cpinResult = NULL; + + ret = RIL_SIM_READY; + +done: + at_response_free(p_response); + return ret; +} + + +/** + * SIM ready means any commands that access the SIM will work, including: + * AT+CPIN, AT+CSMS, AT+CNMI, AT+CRSM + * (all SMS-related commands) + */ + +static void pollSIMState (void *param) +{ + ATResponse *p_response; + int ret; + + if (sState != RADIO_STATE_SIM_NOT_READY) { + // no longer valid to poll + return; + } + + switch(getSIMStatus()) { + case RIL_SIM_ABSENT: + case RIL_SIM_PIN: + case RIL_SIM_PUK: + case RIL_SIM_NETWORK_PERSONALIZATION: + default: + setRadioState(RADIO_STATE_SIM_LOCKED_OR_ABSENT); + return; + + case RIL_SIM_NOT_READY: + RIL_requestTimedCallback (pollSIMState, NULL, &TIMEVAL_SIMPOLL); + return; + + case RIL_SIM_READY: + setRadioState(RADIO_STATE_SIM_READY); + return; + } +} + +/** returns 1 if on, 0 if off, and -1 on error */ +static int isRadioOn() +{ + ATResponse *p_response = NULL; + int err; + char *line; + char ret; + + err = at_send_command_singleline("AT+CFUN?", "+CFUN:", &p_response); + + if (err < 0 || p_response->success == 0) { + // assume radio is off + goto error; + } + + line = p_response->p_intermediates->line; + + err = at_tok_start(&line); + if (err < 0) goto error; + + err = at_tok_nextbool(&line, &ret); + if (err < 0) goto error; + + at_response_free(p_response); + + return (int)ret; + +error: + + at_response_free(p_response); + return -1; +} + +/** + * Initialize everything that can be configured while we're still in + * AT+CFUN=0 + */ +static void initializeCallback(void *param) +{ + ATResponse *p_response = NULL; + int err; + + setRadioState (RADIO_STATE_OFF); + + at_handshake(); + + /* note: we don't check errors here. Everything important will + be handled in onATTimeout and onATReaderClosed */ + + /* atchannel is tolerant of echo but it must */ + /* have verbose result codes */ + at_send_command("ATE0Q0V1", NULL); + + /* No auto-answer */ + at_send_command("ATS0=0", NULL); + + /* Extended errors */ + at_send_command("AT+CMEE=1", NULL); + + /* Network registration events */ + err = at_send_command("AT+CREG=2", &p_response); + + /* some handsets -- in tethered mode -- don't support CREG=2 */ + if (err < 0 || p_response->success == 0) { + at_send_command("AT+CREG=1", NULL); + } + + at_response_free(p_response); + + /* GPRS registration events */ + at_send_command("AT+CGREG=1", NULL); + + /* Call Waiting notifications */ + at_send_command("AT+CCWA=1", NULL); + + /* Alternating voice/data off */ + at_send_command("AT+CMOD=0", NULL); + + /* Not muted */ + at_send_command("AT+CMUT=0", NULL); + + /* +CSSU unsolicited supp service notifications */ + at_send_command("AT+CSSN=0,1", NULL); + + /* no connected line identification */ + at_send_command("AT+COLP=0", NULL); + + /* HEX character set */ + at_send_command("AT+CSCS=\"HEX\"", NULL); + + /* USSD unsolicited */ + at_send_command("AT+CUSD=1", NULL); + + /* Enable +CGEV GPRS event notifications, but don't buffer */ + at_send_command("AT+CGEREP=1,0", NULL); + + /* SMS PDU mode */ + at_send_command("AT+CMGF=0", NULL); + +#ifdef USE_TI_COMMANDS + + at_send_command("AT%CPI=3", NULL); + + /* TI specific -- notifications when SMS is ready (currently ignored) */ + at_send_command("AT%CSTAT=1", NULL); + +#endif /* USE_TI_COMMANDS */ + + + /* assume radio is off on error */ + if (isRadioOn() > 0) { + setRadioState (RADIO_STATE_SIM_NOT_READY); + } +} + +static void waitForClose() +{ + pthread_mutex_lock(&s_state_mutex); + + while (s_closed == 0) { + pthread_cond_wait(&s_state_cond, &s_state_mutex); + } + + pthread_mutex_unlock(&s_state_mutex); +} + +/** + * Called by atchannel when an unsolicited line appears + * This is called on atchannel's reader thread. AT commands may + * not be issued here + */ +static void onUnsolicited (const char *s, const char *sms_pdu) +{ + char *line = NULL; + int err; + + /* Ignore unsolicited responses until we're initialized. + * This is OK because the RIL library will poll for initial state + */ + if (sState == RADIO_STATE_UNAVAILABLE) { + return; + } + + if (strStartsWith(s, "%CTZV:")) { + /* TI specific -- NITZ time */ + char *response; + + line = strdup(s); + at_tok_start(&line); + + err = at_tok_nextstr(&line, &response); + + if (err != 0) { + LOGE("invalid NITZ line %s\n", s); + } else { + RIL_onUnsolicitedResponse ( + RIL_UNSOL_NITZ_TIME_RECEIVED, + response, strlen(response)); + } + } else if (strStartsWith(s,"+CRING:") + || strStartsWith(s,"RING") + || strStartsWith(s,"NO CARRIER") + || strStartsWith(s,"+CCWA") + ) { + RIL_onUnsolicitedResponse ( + RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, + NULL, 0); +#ifdef WORKAROUND_FAKE_CGEV + RIL_requestTimedCallback (onPDPContextListChanged, NULL, NULL); +#endif /* WORKAROUND_FAKE_CGEV */ + } else if (strStartsWith(s,"+CREG:") + || strStartsWith(s,"+CGREG:") + ) { + RIL_onUnsolicitedResponse ( + RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED, + NULL, 0); +#ifdef WORKAROUND_FAKE_CGEV + RIL_requestTimedCallback (onPDPContextListChanged, NULL, NULL); +#endif /* WORKAROUND_FAKE_CGEV */ + } else if (strStartsWith(s, "+CMT:")) { + RIL_onUnsolicitedResponse ( + RIL_UNSOL_RESPONSE_NEW_SMS, + sms_pdu, strlen(sms_pdu)); + } else if (strStartsWith(s, "+CDS:")) { + RIL_onUnsolicitedResponse ( + RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT, + sms_pdu, strlen(sms_pdu)); + } else if (strStartsWith(s, "+CGEV:")) { + /* Really, we can ignore NW CLASS and ME CLASS events here, + * but right now we don't since extranous + * RIL_UNSOL_PDP_CONTEXT_LIST_CHANGED calls are tolerated + */ + /* can't issue AT commands here -- call on main thread */ + RIL_requestTimedCallback (onPDPContextListChanged, NULL, NULL); +#ifdef WORKAROUND_FAKE_CGEV + } else if (strStartsWith(s, "+CME ERROR: 150")) { + RIL_requestTimedCallback (onPDPContextListChanged, NULL, NULL); +#endif /* WORKAROUND_FAKE_CGEV */ + } +} + +/* Called on command or reader thread */ +static void onATReaderClosed() +{ + LOGI("AT channel closed\n"); + at_close(); + s_closed = 1; + + setRadioState (RADIO_STATE_UNAVAILABLE); +} + +/* Called on command thread */ +static void onATTimeout() +{ + LOGI("AT channel timeout; closing\n"); + at_close(); + + s_closed = 1; + + /* FIXME cause a radio reset here */ + + setRadioState (RADIO_STATE_UNAVAILABLE); +} + +static void usage(char *s) +{ +#ifdef RIL_SHLIB + fprintf(stderr, "reference-ril requires: -p <tcp port> or -d /dev/tty_device\n"); +#else + fprintf(stderr, "usage: %s [-p <tcp port>] [-d /dev/tty_device]\n", s); + exit(-1); +#endif +} + +static void * +mainLoop(void *param) +{ + int fd; + int ret; + + AT_DUMP("== ", "entering mainLoop()", -1 ); + at_set_on_reader_closed(onATReaderClosed); + at_set_on_timeout(onATTimeout); + + for (;;) { + fd = -1; + while (fd < 0) { + if (s_port > 0) { + fd = socket_loopback_client(s_port, SOCK_STREAM); + } else if (s_device_socket) { + fd = socket_local_client( s_device_path, + ANDROID_SOCKET_NAMESPACE_FILESYSTEM, + SOCK_STREAM ); + } else if (s_device_path != NULL) { + fd = open (s_device_path, O_RDWR); + if ( fd >= 0 && !memcmp( s_device_path, "/dev/ttyS", 9 ) ) { + /* disable echo on serial ports */ + struct termios ios; + tcgetattr( fd, &ios ); + ios.c_lflag = 0; /* disable ECHO, ICANON, etc... */ + tcsetattr( fd, TCSANOW, &ios ); + } + } + + if (fd < 0) { + perror ("opening AT interface. retrying..."); + sleep(10); + /* never returns */ + } + } + + s_closed = 0; + ret = at_open(fd, onUnsolicited); + + if (ret < 0) { + LOGE ("AT error %d on at_open\n", ret); + return 0; + } + + RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0); + + // Give initializeCallback a chance to dispatched, since + // we don't presently have a cancellation mechanism + sleep(1); + + waitForClose(); + LOGI("Re-opening after close"); + } +} + +#ifdef RIL_SHLIB + +pthread_t s_tid_mainloop; + +const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv) +{ + int ret; + int fd = -1; + int opt; + pthread_attr_t attr; + + s_rilenv = env; + + while ( -1 != (opt = getopt(argc, argv, "p:d:s:"))) { + switch (opt) { + case 'p': + s_port = atoi(optarg); + if (s_port == 0) { + usage(argv[0]); + return NULL; + } + LOGI("Opening loopback port %d\n", s_port); + break; + + case 'd': + s_device_path = optarg; + LOGI("Opening tty device %s\n", s_device_path); + break; + + case 's': + s_device_path = optarg; + s_device_socket = 1; + LOGI("Opening socket %s\n", s_device_path); + break; + + default: + usage(argv[0]); + return NULL; + } + } + + if (s_port < 0 && s_device_path == NULL) { + usage(argv[0]); + return NULL; + } + + pthread_attr_init (&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL); + + return &s_callbacks; +} +#else /* RIL_SHLIB */ +int main (int argc, char **argv) +{ + int ret; + int fd = -1; + int opt; + + while ( -1 != (opt = getopt(argc, argv, "p:d:"))) { + switch (opt) { + case 'p': + s_port = atoi(optarg); + if (s_port == 0) { + usage(argv[0]); + } + LOGI("Opening loopback port %d\n", s_port); + break; + + case 'd': + s_device_path = optarg; + LOGI("Opening tty device %s\n", s_device_path); + break; + + case 's': + s_device_path = optarg; + s_device_socket = 1; + LOGI("Opening socket %s\n", s_device_path); + break; + + default: + usage(argv[0]); + } + } + + if (s_port < 0 && s_device_path == NULL) { + usage(argv[0]); + } + + RIL_register(&s_callbacks); + + mainLoop(NULL); + + return 0; +} + +#endif /* RIL_SHLIB */ |