goldfish: add ril
Create goldfish's own ril so it is not coupled
with reference-ril, and we can further customize it
for goldfish's need
BUG: 110047214
This initial version is taken from hardware/ril
commit 982ba7ab0767df77b0cda7f592bfde7e8b6cb53d
Author: Sooraj Sasindran <sasindran@google.com>
Date: Mon May 7 21:20:43 2018 -0700
Add scan error to Network Scan result
Merged-In: I07a938a8a6e9af368f51540f2c5f9627099017d7
(cherry picked from commit 8506e4eab86b62a662770a08081e599ab79bae02)
diff --git a/ril/atchannel.c b/ril/atchannel.c
new file mode 100644
index 0000000..0041836
--- /dev/null
+++ b/ril/atchannel.c
@@ -0,0 +1,975 @@
+/* //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>
+
+#include "misc.h"
+
+
+#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;
+
+#if AT_DEBUG
+void AT_DUMP(const char* prefix, const char* buff, int len)
+{
+ if (len < 0)
+ len = strlen(buff);
+ RLOGD("%.*s", len, buff);
+}
+#endif
+
+/*
+ * There is one reader thread |s_tid_reader| and potentially multiple writer
+ * threads. |s_commandmutex| and |s_commandcond| are used to maintain the
+ * condition that the writer thread will not read from |sp_response| until the
+ * reader thread has signaled itself is finished, etc. |s_writeMutex| is used to
+ * prevent multiple writer threads from calling at_send_command_full_nolock
+ * function at the same time.
+ */
+
+static pthread_mutex_t s_commandmutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t s_commandcond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t s_writeMutex = PTHREAD_MUTEX_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);
+
+#define NS_PER_S 1000000000
+static void setTimespecRelative(struct timespec *p_ts, long long msec)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, (struct timezone *) NULL);
+
+ p_ts->tv_sec = tv.tv_sec + (msec / 1000);
+ p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L;
+ /* assuming tv.tv_usec < 10^6 */
+ if (p_ts->tv_nsec >= NS_PER_S) {
+ p_ts->tv_sec++;
+ p_ts->tv_nsec -= NS_PER_S;
+ }
+}
+
+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 */
+ RLOGE("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)) {
+ RLOGE("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 );
+
+ 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) {
+ RLOGD("atchannel: EOF reached");
+ } else {
+ RLOGD("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 */
+
+ RLOGD("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 __unused)
+{
+ 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) {
+ free(line1);
+ break;
+ }
+
+ if (s_unsolHandler != NULL) {
+ s_unsolHandler (line1, line2);
+ }
+ free(line1);
+ } else {
+ processLine(line);
+ }
+ }
+
+ 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;
+ }
+
+ RLOGD("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;
+ }
+
+ RLOGD("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;
+
+ 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;
+ struct timespec ts;
+
+ 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();
+
+ if (timeoutMsec != 0) {
+ setTimespecRelative(&ts, timeoutMsec);
+ }
+
+ while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
+ if (timeoutMsec != 0) {
+ err = pthread_cond_timedwait(&s_commandcond, &s_commandmutex, &ts);
+ } 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;
+ bool inEmulator;
+
+ if (0 != pthread_equal(s_tid_reader, pthread_self())) {
+ /* cannot be called from reader thread */
+ return AT_ERROR_INVALID_THREAD;
+ }
+ inEmulator = isInEmulator();
+ if (inEmulator) {
+ pthread_mutex_lock(&s_writeMutex);
+ }
+ pthread_mutex_lock(&s_commandmutex);
+
+ err = at_send_command_full_nolock(command, type,
+ responsePrefix, smspdu,
+ timeoutMsec, pp_outResponse);
+
+ pthread_mutex_unlock(&s_commandmutex);
+ if (inEmulator) {
+ pthread_mutex_unlock(&s_writeMutex);
+ }
+
+ 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;
+ bool inEmulator;
+
+ if (0 != pthread_equal(s_tid_reader, pthread_self())) {
+ /* cannot be called from reader thread */
+ return AT_ERROR_INVALID_THREAD;
+ }
+ inEmulator = isInEmulator();
+ if (inEmulator) {
+ pthread_mutex_lock(&s_writeMutex);
+ }
+ 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);
+ if (inEmulator) {
+ pthread_mutex_unlock(&s_writeMutex);
+ }
+
+ 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;
+}
+