| /* //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> | 
 |  | 
 | #include "mbtk_log.h" | 
 | #include "mbtk_type.h" | 
 | #include "mbtk_utils.h" | 
 |  | 
 | #define MAX_AT_RESPONSE (8 * 1024) | 
 | #define HANDSHAKE_RETRY_COUNT 20 | 
 | #define HANDSHAKE_TIMEOUT_MSEC 500 | 
 | #define AT_BUFF_MAX 1024 | 
 |  | 
 | static pthread_t s_tid_reader; | 
 | static int s_at_fd = -1;    /* fd of the AT channel */ | 
 | static int s_uart_fd = -1;    /* fd of the UART channel */ | 
 |  | 
 | static ATUnsolHandler s_unsolHandler; | 
 |  | 
 | /* for input buffering */ | 
 |  | 
 | static char s_ATBuffer[MAX_AT_RESPONSE+1]; | 
 | static char *s_ATBufferCur = s_ATBuffer; | 
 | static char s_UartBuffer[MAX_AT_RESPONSE+1]; | 
 | static char *s_UartBufferCur = s_UartBuffer; | 
 |  | 
 | static mbtk_ril_at_state_enum at_state = RIL_AT_STATE_CLOSED; | 
 |  | 
 | #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 | 
 |  | 
 | /* | 
 |  * 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. | 
 |  */ | 
 |  | 
 | // "Wait" when AT process... | 
 | 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 char s_curr_at[AT_BUFF_MAX]; | 
 |  | 
 | 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); | 
 |  | 
 | typedef struct | 
 | { | 
 |     char *at_command; | 
 |     long long timeout;  // ms | 
 |     bool timeout_close; // Close AT or not while AT response timeout. | 
 | } at_timeout_t; | 
 |  | 
 | static at_timeout_t at_timeout_list[] = | 
 | { | 
 |     {"AT+CRSM", 10000, false}, | 
 | //    {"AT+COPS", 60000, false} | 
 | }; | 
 |  | 
 | #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); | 
 |  | 
 | //    LOGD("line:%s", line); | 
 | //    LOGD("line-1:%s", p_new->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! */ // Only for ATD ? | 
 |     "NO ANSWER", | 
 |     "NO DIALTONE", | 
 | }; | 
 | static int isFinalResponseError(const char *line) | 
 | { | 
 |     size_t i; | 
 |  | 
 |     for (i = 0 ; i < ARRAY_SIZE(s_finalResponsesError) ; i++) | 
 |     { | 
 |         if (strStartsWith(line, s_finalResponsesError[i])) | 
 |         { | 
 |             return 1; | 
 |         } | 
 |     } | 
 |  | 
 |     if(!strncasecmp(s_curr_at, "ATD", 3) && strStartsWith(line, "NO CARRIER")) | 
 |     { | 
 |         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 < ARRAY_SIZE(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 < ARRAY_SIZE(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); | 
 |  | 
 |     //LOGD("AT complete (pthread_cond_signal): %s",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); | 
 | //    LOGD("LINE : %s", line); | 
 |     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) | 
 |                    ) | 
 |                 { | 
 |                     if(*line == '"') | 
 |                     { | 
 |                         char *line_temp = strdup(line); | 
 |                         line_temp++; | 
 |                         if(strlen(line_temp) > 0) | 
 |                         { | 
 |                             char *ptr = line_temp + strlen(line_temp) - 1; | 
 |                             while(ptr >= line_temp && *ptr == '"') | 
 |                             { | 
 |                                 *ptr = '\0'; | 
 |                                 ptr--; | 
 |                             } | 
 |                         } | 
 |                         addIntermediate(line_temp); | 
 |                         free(line_temp); | 
 |                     } | 
 |                     else | 
 |                     { | 
 |                         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_at_fd, p_read, | 
 |                          MAX_AT_RESPONSE - (p_read - s_ATBuffer)); | 
 |             usleep(10000); | 
 |         } | 
 |         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) | 
 |             { | 
 |                 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", ret); | 
 |     return ret; | 
 | } | 
 |  | 
 | static const char *readlineUrc() | 
 | { | 
 |     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_UartBufferCur == '\0') | 
 |     { | 
 |         /* empty buffer */ | 
 |         s_UartBufferCur = s_UartBuffer; | 
 |         *s_UartBufferCur = '\0'; | 
 |         p_read = s_UartBuffer; | 
 |     } | 
 |     else       /* *s_ATBufferCur != '\0' */ | 
 |     { | 
 |         /* there's data in the buffer from the last read */ | 
 |  | 
 |         // skip over leading newlines | 
 |         while (*s_UartBufferCur == '\r' || *s_UartBufferCur == '\n') | 
 |             s_UartBufferCur++; | 
 |  | 
 |         p_eol = findNextEOL(s_UartBufferCur); | 
 |  | 
 |         if (p_eol == NULL) | 
 |         { | 
 |             /* a partial line. move it up and prepare to read more */ | 
 |             size_t len; | 
 |  | 
 |             len = strlen(s_UartBufferCur); | 
 |  | 
 |             memmove(s_UartBuffer, s_UartBufferCur, len + 1); | 
 |             p_read = s_UartBuffer + len; | 
 |             s_UartBufferCur = s_UartBuffer; | 
 |         } | 
 |         /* 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_UartBuffer)) | 
 |         { | 
 |             LOGE("ERROR: Input line exceeded buffer\n"); | 
 |             /* ditch buffer and start over again */ | 
 |             s_UartBufferCur = s_UartBuffer; | 
 |             *s_UartBufferCur = '\0'; | 
 |             p_read = s_UartBuffer; | 
 |         } | 
 |  | 
 |         do | 
 |         { | 
 |             count = read(s_uart_fd, p_read, | 
 |                          MAX_AT_RESPONSE - (p_read - s_UartBuffer)); | 
 |             usleep(10000); | 
 |         } | 
 |         while (count < 0 && errno == EINTR); | 
 |  | 
 |         if (count > 0) | 
 |         { | 
 |             AT_DUMP( "<< ", p_read, count ); | 
 |  | 
 |             p_read[count] = '\0'; | 
 |  | 
 |             // skip over leading newlines | 
 |             while (*s_UartBufferCur == '\r' || *s_UartBufferCur == '\n') | 
 |                 s_UartBufferCur++; | 
 |  | 
 |             p_eol = findNextEOL(s_UartBufferCur); | 
 |             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_UartBufferCur; | 
 |     *p_eol = '\0'; | 
 |     s_UartBufferCur = p_eol + 1; /* this will always be <= p_read,    */ | 
 |     /* and there will be a \0 at *p_read */ | 
 |  | 
 |     LOGD("URC< %s", ret); | 
 |     return ret; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static void onReaderClosed() | 
 | { | 
 |     LOGD("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(); | 
 |     } | 
 | } | 
 |  | 
 | typedef struct | 
 | { | 
 |     int cid; | 
 |     bool act; | 
 |     bool waitting; | 
 | } info_cgact_wait_t; | 
 | extern info_cgact_wait_t cgact_wait; | 
 |  | 
 | static void *readerLoop(void *arg) | 
 | { | 
 |     UNUSED(arg); | 
 |     for (;;) | 
 |     { | 
 |         const char * line; | 
 |  | 
 |         line = readline(); | 
 |  | 
 |         if (line == NULL) | 
 |         { | 
 |             break; | 
 |         } | 
 |  | 
 |         if(strStartsWith(line, "MBTK_AT_READY")) { | 
 |             //handleUnsolicited(line); | 
 |             continue; | 
 |         } else if(strStartsWith(line, "CONNECT")) { | 
 |             if(cgact_wait.waitting && cgact_wait.act) { | 
 |                 cgact_wait.waitting = false; | 
 |             } | 
 |         } | 
 |  | 
 |         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; | 
 | } | 
 |  | 
 | static void *readerUrcLoop(void *arg) | 
 | { | 
 |     UNUSED(arg); | 
 |     for (;;) | 
 |     { | 
 |         const char *line; | 
 |  | 
 |         line = readlineUrc(); | 
 |  | 
 |         if (line == NULL) | 
 |         { | 
 |             break; | 
 |         } | 
 |  | 
 |         handleUnsolicited(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_at_fd < 0 || s_readerClosed > 0) | 
 |     { | 
 |         return AT_ERROR_CHANNEL_CLOSED; | 
 |     } | 
 |  | 
 |     LOGD("AT> %s", s); | 
 |  | 
 |     AT_DUMP( ">> ", s, strlen(s) ); | 
 |  | 
 |     memset(s_curr_at, 0x0, AT_BUFF_MAX); | 
 |     memcpy(s_curr_at, s, strlen(s)); | 
 |  | 
 |     /* the main string */ | 
 |     while (cur < len) | 
 |     { | 
 |         do | 
 |         { | 
 |             written = write (s_at_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_at_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_at_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_at_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_at_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 at_fd, int uart_fd, ATUnsolHandler h) | 
 | { | 
 |     int ret; | 
 |     pthread_attr_t attr; | 
 |  | 
 |     s_at_fd = at_fd; | 
 |     s_uart_fd = uart_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, NULL); | 
 |     if (ret < 0) | 
 |     { | 
 |         LOGE("AT thread create fail."); | 
 |         return -1; | 
 |     } | 
 |  | 
 |     pthread_t uart_tid_reader; | 
 |     ret = pthread_create(&uart_tid_reader, &attr, readerUrcLoop, NULL); | 
 |     if (ret < 0) | 
 |     { | 
 |         LOGE("Uart thread create fail."); | 
 |         return -1; | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | /* FIXME is it ok to call this from the reader and the command thread? */ | 
 | void at_close() | 
 | { | 
 |     LOGD("at_close()"); | 
 |     if (s_at_fd >= 0) | 
 |     { | 
 |         close(s_at_fd); | 
 |     } | 
 |     if (s_uart_fd >= 0) | 
 |     { | 
 |         close(s_uart_fd); | 
 |     } | 
 |     s_at_fd = -1; | 
 |     s_uart_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 */ | 
 |  | 
 |     at_state = RIL_AT_STATE_CLOSED; | 
 | } | 
 |  | 
 | 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; | 
 |     } | 
 | } | 
 |  | 
 | static long long at_timeout_get(const char *at_command, bool *timeout_close) | 
 | { | 
 |     long long timeout = 0; | 
 |     int i; | 
 |     for(i = 0; i <  ARRAY_SIZE(at_timeout_list); i++) | 
 |     { | 
 |         if(!strncasecmp(at_command, at_timeout_list[i].at_command, strlen(at_timeout_list[i].at_command))) | 
 |         { | 
 |             timeout = at_timeout_list[i].timeout; | 
 |             *timeout_close = at_timeout_list[i].timeout_close; | 
 |             break; | 
 |         } | 
 |     } | 
 |  | 
 |     return timeout; | 
 | } | 
 |  | 
 | /** | 
 |  * 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; | 
 |     bool tiemout_close = true; | 
 |     struct timespec ts; | 
 |     if(at_state == RIL_AT_STATE_READY) | 
 |         at_state = RIL_AT_STATE_BUSY; | 
 |  | 
 |     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) | 
 |     { | 
 |         timeoutMsec = at_timeout_get(command, &tiemout_close); | 
 |     } | 
 |  | 
 |     if (timeoutMsec != 0) | 
 |     { | 
 |         setTimespecRelative(&ts, timeoutMsec); | 
 |     } | 
 |  | 
 |     while (sp_response->finalResponse == NULL && s_readerClosed == 0) | 
 |     { | 
 |         //LOGD("AT wait time:%lld",timeoutMsec); | 
 |         if (timeoutMsec != 0) | 
 |         { | 
 |             err = pthread_cond_timedwait(&s_commandcond, &s_commandmutex, &ts); | 
 |         } | 
 |         else | 
 |         { | 
 |             err = pthread_cond_wait(&s_commandcond, &s_commandmutex); | 
 |         } | 
 |  | 
 |         //LOGD("AT continue:err - %d",err); | 
 |         if (err == ETIMEDOUT) | 
 |         { | 
 |             if(tiemout_close) | 
 |             { | 
 |                 err = AT_ERROR_TIMEOUT_CLOSE; | 
 |             } | 
 |             else | 
 |             { | 
 |                 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: | 
 |     if(at_state == RIL_AT_STATE_BUSY) | 
 |         at_state = RIL_AT_STATE_READY; | 
 |     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 */ | 
 |         LOGE("cannot be called from reader thread."); | 
 |         return AT_ERROR_INVALID_THREAD; | 
 |     } | 
 |  | 
 |     // Waitting for previous AT complete. | 
 |     while(at_state == RIL_AT_STATE_BUSY) | 
 |     { | 
 |         usleep(10000); | 
 |     } | 
 |  | 
 |     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_CLOSE && 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) | 
 | { | 
 |     return at_send_command_full (command, NO_RESULT, NULL, | 
 |                                  NULL, 0, pp_outResponse); | 
 | } | 
 |  | 
 |  | 
 | 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_singleline_with_timeout (const char *command, | 
 |         const char *responsePrefix, | 
 |         ATResponse **pp_outResponse,long long timeoutMsec) | 
 | { | 
 |     int err; | 
 |  | 
 |     err = at_send_command_full (command, SINGLELINE, responsePrefix, | 
 |                                 NULL, timeoutMsec, 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); | 
 |  | 
 | #if 0 | 
 |     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; | 
 |         } | 
 |     } | 
 | #else | 
 |     err = at_send_command_full_nolock("ATE0Q0V1", NO_RESULT, | 
 |                                           NULL, NULL, 0, NULL); | 
 | #endif | 
 |  | 
 |     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 | 
 |  */ | 
 | int 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 ret; | 
 | } | 
 |  | 
 | mbtk_ril_at_state_enum at_state_get() | 
 | { | 
 |     return at_state; | 
 | } | 
 |  | 
 | void at_state_set(mbtk_ril_at_state_enum state) | 
 | { | 
 |     at_state = state; | 
 | } | 
 |  | 
 | bool at_rsp_check(ATResponse *p_response) | 
 | { | 
 |     if(!p_response || !p_response->success) | 
 |         return false; | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | void unused_func() | 
 | { | 
 |     isFinalResponse(NULL); | 
 | } | 
 |  |