| /* //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[ATPORTID_NUM]; |
| static int s_at_fd[ATPORTID_NUM] = {-1}; /* fd of the AT channel */ |
| static int s_uart_fd[MBTK_SIM_NUM] = {-1}; /* fd of the UART channel */ |
| |
| static ATUnsolHandler s_unsolHandler[MBTK_SIM_NUM]; |
| |
| /* for input buffering */ |
| |
| static char s_ATBuffer[ATPORTID_NUM][MAX_AT_RESPONSE+1]; |
| static char *s_ATBufferCur[ATPORTID_NUM] = {s_ATBuffer[ATPORTID_SIM1_0], s_ATBuffer[ATPORTID_SIM1_0], s_ATBuffer[ATPORTID_SIM1_0], |
| s_ATBuffer[ATPORTID_SIM2_0], s_ATBuffer[ATPORTID_SIM2_0], s_ATBuffer[ATPORTID_SIM2_0]}; |
| static char s_UartBuffer[MBTK_SIM_NUM][MAX_AT_RESPONSE+1]; |
| static char *s_UartBufferCur[MBTK_SIM_NUM] = {s_UartBuffer[MBTK_SIM_1], s_UartBuffer[MBTK_SIM_2]}; |
| |
| static mbtk_ril_at_state_enum at_state[ATPORTID_NUM] = {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[ATPORTID_NUM] = {PTHREAD_MUTEX_INITIALIZER}; |
| static pthread_cond_t s_commandcond[ATPORTID_NUM] = {PTHREAD_COND_INITIALIZER}; |
| |
| static ATCommandType s_type[ATPORTID_NUM]; |
| static const char *s_responsePrefix[ATPORTID_NUM] = {NULL}; |
| static const char *s_smsPDU[ATPORTID_NUM] = {NULL}; |
| static ATResponse *sp_response[ATPORTID_NUM] = {NULL}; |
| static char s_curr_at[ATPORTID_NUM][AT_BUFF_MAX]; |
| |
| static void (*s_onTimeout)(void) = NULL; |
| static void (*s_onReaderClosed)(void) = NULL; |
| static int s_readerClosed; |
| |
| static void onReaderClosed(ATPortId_enum port); |
| static int writeCtrlZ (ATPortId_enum port, const char *s); |
| static int writeline (ATPortId_enum port, 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(ATPortId_enum port, 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[port]->p_intermediates; |
| sp_response[port]->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(ATPortId_enum port, 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[port], "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(ATPortId_enum port, const char *line) |
| { |
| return isFinalResponseSuccess(line) || isFinalResponseError(port, 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(ATPortId_enum port, const char *line) |
| { |
| sp_response[port]->finalResponse = strdup(line); |
| |
| //LOGD("AT complete (pthread_cond_signal): %s",line); |
| pthread_cond_signal(&s_commandcond[port]); |
| } |
| |
| static void handleUnsolicited(ATPortId_enum port, const char *line) |
| { |
| if (s_unsolHandler[port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2] != NULL) |
| { |
| s_unsolHandler[port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2](line, NULL); |
| } |
| } |
| |
| static void processLine(ATPortId_enum port, const char *line) |
| { |
| pthread_mutex_lock(&s_commandmutex[port]); |
| // LOGD("LINE : %s", line); |
| if (sp_response[port] == NULL) |
| { |
| /* no command pending */ |
| handleUnsolicited(port, line); |
| } |
| else if (isFinalResponseSuccess(line)) |
| { |
| sp_response[port]->success = 1; |
| handleFinalResponse(port, line); |
| } |
| else if (isFinalResponseError(port, line)) |
| { |
| sp_response[port]->success = 0; |
| handleFinalResponse(port, line); |
| } |
| else if (s_smsPDU[port] != NULL && 0 == strcmp(line, "> ")) |
| { |
| // See eg. TS 27.005 4.3 |
| // Commands like AT+CMGS have a "> " prompt |
| writeCtrlZ(port, s_smsPDU[port]); |
| s_smsPDU[port] = NULL; |
| } |
| else switch (s_type[port]) |
| { |
| case NO_RESULT: |
| handleUnsolicited(port, line); |
| break; |
| case NUMERIC: |
| if (sp_response[port]->p_intermediates == NULL |
| && isdigit(line[0]) |
| ) |
| { |
| addIntermediate(port, line); |
| } |
| else |
| { |
| /* either we already have an intermediate response or |
| the line doesn't begin with a digit */ |
| handleUnsolicited(port, line); |
| } |
| break; |
| case SINGLELINE: |
| if (sp_response[port]->p_intermediates == NULL |
| && strStartsWith (line, s_responsePrefix[port]) |
| ) |
| { |
| if(*line == '"') |
| { |
| char *line_temp = strdup(line); |
| char *free_ptr = line_temp; |
| line_temp++; |
| if(strlen(line_temp) > 0) |
| { |
| char *ptr = line_temp + strlen(line_temp) - 1; |
| while(ptr >= line_temp && *ptr == '"') |
| { |
| *ptr = '\0'; |
| ptr--; |
| } |
| } |
| addIntermediate(port, line_temp); |
| free(free_ptr); |
| } |
| else |
| { |
| addIntermediate(port, line); |
| } |
| } |
| else |
| { |
| /* we already have an intermediate response */ |
| handleUnsolicited(port, line); |
| } |
| break; |
| case MULTILINE: |
| if (strStartsWith (line, s_responsePrefix[port])) |
| { |
| addIntermediate(port, line); |
| } |
| else |
| { |
| handleUnsolicited(port, line); |
| } |
| break; |
| |
| default: /* this should never be reached */ |
| LOGE("Unsupported AT command type %d\n", s_type[port]); |
| handleUnsolicited(port, line); |
| break; |
| } |
| |
| pthread_mutex_unlock(&s_commandmutex[port]); |
| } |
| |
| |
| /** |
| * 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(ATPortId_enum port) |
| { |
| 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[port] == '\0') |
| { |
| /* empty buffer */ |
| s_ATBufferCur[port] = s_ATBuffer[port]; |
| *s_ATBufferCur[port] = '\0'; |
| p_read = s_ATBuffer[port]; |
| } |
| else /* *s_ATBufferCur != '\0' */ |
| { |
| /* there's data in the buffer from the last read */ |
| |
| // skip over leading newlines |
| while (*s_ATBufferCur[port] == '\r' || *s_ATBufferCur[port] == '\n') |
| s_ATBufferCur[port]++; |
| |
| p_eol = findNextEOL(s_ATBufferCur[port]); |
| |
| if (p_eol == NULL) |
| { |
| /* a partial line. move it up and prepare to read more */ |
| size_t len; |
| |
| len = strlen(s_ATBufferCur[port]); |
| |
| memmove(s_ATBuffer[port], s_ATBufferCur[port], len + 1); |
| p_read = s_ATBuffer[port] + len; |
| s_ATBufferCur[port] = s_ATBuffer[port]; |
| } |
| /* 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[port])) |
| { |
| LOGE("ERROR: Input line exceeded buffer\n"); |
| /* ditch buffer and start over again */ |
| s_ATBufferCur[port] = s_ATBuffer[port]; |
| *s_ATBufferCur[port] = '\0'; |
| p_read = s_ATBuffer[port]; |
| } |
| |
| do |
| { |
| count = read(s_at_fd[port], p_read, |
| MAX_AT_RESPONSE - (p_read - s_ATBuffer[port])); |
| 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[port] == '\r' || *s_ATBufferCur[port] == '\n') |
| s_ATBufferCur[port]++; |
| |
| p_eol = findNextEOL(s_ATBufferCur[port]); |
| p_read += count; |
| } |
| else if (count <= 0) |
| { |
| /* read error encountered or EOF reached */ |
| if(count == 0) |
| { |
| LOGD("atchannel[Port-%d]: EOF reached", port); |
| } |
| else |
| { |
| LOGD("atchannel[Port-%d]: read error %s", port, strerror(errno)); |
| } |
| return NULL; |
| } |
| } |
| |
| /* a full line in the buffer. Place a \0 over the \r and return */ |
| |
| ret = s_ATBufferCur[port]; |
| *p_eol = '\0'; |
| s_ATBufferCur[port] = p_eol + 1; /* this will always be <= p_read, */ |
| /* and there will be a \0 at *p_read */ |
| |
| LOGD("[Port-%d]AT< %s", port, ret); |
| return ret; |
| } |
| |
| static const char *readlineUrc(ATPortId_enum port) |
| { |
| ssize_t count; |
| |
| char *p_read = NULL; |
| char *p_eol = NULL; |
| char *ret; |
| |
| mbtk_sim_type_enum sim_id = port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2; |
| |
| |
| /* 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[sim_id] == '\0') |
| { |
| /* empty buffer */ |
| s_UartBufferCur[sim_id] = s_UartBuffer[sim_id]; |
| *s_UartBufferCur[sim_id] = '\0'; |
| p_read = s_UartBuffer[sim_id]; |
| } |
| else /* *s_ATBufferCur != '\0' */ |
| { |
| /* there's data in the buffer from the last read */ |
| |
| // skip over leading newlines |
| while (*s_UartBufferCur[sim_id] == '\r' || *s_UartBufferCur[sim_id] == '\n') |
| s_UartBufferCur[sim_id]++; |
| |
| p_eol = findNextEOL(s_UartBufferCur[sim_id]); |
| |
| if (p_eol == NULL) |
| { |
| /* a partial line. move it up and prepare to read more */ |
| size_t len; |
| |
| len = strlen(s_UartBufferCur[sim_id]); |
| |
| memmove(s_UartBuffer[sim_id], s_UartBufferCur[sim_id], len + 1); |
| p_read = s_UartBuffer[sim_id] + len; |
| s_UartBufferCur[sim_id] = s_UartBuffer[sim_id]; |
| } |
| /* 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[sim_id])) |
| { |
| LOGE("ERROR: Input line exceeded buffer\n"); |
| /* ditch buffer and start over again */ |
| s_UartBufferCur[sim_id] = s_UartBuffer[sim_id]; |
| *s_UartBufferCur[sim_id] = '\0'; |
| p_read = s_UartBuffer[sim_id]; |
| } |
| |
| do |
| { |
| count = read(s_uart_fd[sim_id], p_read, |
| MAX_AT_RESPONSE - (p_read - s_UartBuffer[sim_id])); |
| 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[sim_id] == '\r' || *s_UartBufferCur[sim_id] == '\n') |
| s_UartBufferCur[sim_id]++; |
| |
| p_eol = findNextEOL(s_UartBufferCur[sim_id]); |
| 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[sim_id]; |
| *p_eol = '\0'; |
| s_UartBufferCur[sim_id] = p_eol + 1; /* this will always be <= p_read, */ |
| /* and there will be a \0 at *p_read */ |
| |
| LOGD("[Sim-%d,Port-%d]URC< %s", sim_id, port, ret); |
| return ret; |
| } |
| |
| |
| static void onReaderClosed(ATPortId_enum port) |
| { |
| LOGD("onReaderClosed()"); |
| if (s_onReaderClosed != NULL && s_readerClosed == 0) |
| { |
| |
| pthread_mutex_lock(&s_commandmutex[port]); |
| |
| s_readerClosed = 1; |
| |
| pthread_cond_signal(&s_commandcond[port]); |
| |
| pthread_mutex_unlock(&s_commandmutex[port]); |
| |
| 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); |
| ATPortId_enum *port = (ATPortId_enum*)arg; |
| for (;;) |
| { |
| const char * line; |
| |
| line = readline(*port); |
| |
| if (line == NULL) |
| { |
| //usleep(50000); |
| //continue; |
| 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(*port); |
| |
| if (line2 == NULL) |
| { |
| free(line1); |
| break; |
| } |
| |
| if (s_unsolHandler[*port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2] != NULL) |
| { |
| s_unsolHandler[*port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2] (line1, line2); |
| } |
| free(line1); |
| } |
| else |
| { |
| processLine(*port, line); |
| } |
| } |
| |
| onReaderClosed(*port); |
| |
| free(port); |
| |
| return NULL; |
| } |
| |
| static void *readerUrcLoop(void *arg) |
| { |
| UNUSED(arg); |
| ATPortId_enum *port = (ATPortId_enum*)arg; |
| for (;;) |
| { |
| const char *line; |
| |
| line = readlineUrc(*port); |
| |
| if (line == NULL) |
| { |
| break; |
| } |
| |
| handleUnsolicited(*port, line); |
| } |
| |
| onReaderClosed(*port); |
| |
| free(port); |
| |
| 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 (ATPortId_enum port, const char *s) |
| { |
| size_t cur = 0; |
| size_t len = strlen(s); |
| ssize_t written; |
| |
| if (s_at_fd[port] < 0 || s_readerClosed > 0) |
| { |
| return AT_ERROR_CHANNEL_CLOSED; |
| } |
| |
| LOGD("[Port-%d]AT> %s", port, s); |
| |
| AT_DUMP( ">> ", s, strlen(s) ); |
| |
| memset(s_curr_at[port], 0x0, AT_BUFF_MAX); |
| memcpy(s_curr_at[port], s, strlen(s)); |
| |
| /* the main string */ |
| while (cur < len) |
| { |
| do |
| { |
| written = write (s_at_fd[port], 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[port], "\r", 1); |
| } |
| while ((written < 0 && errno == EINTR) || (written == 0)); |
| |
| if (written < 0) |
| { |
| return AT_ERROR_GENERIC; |
| } |
| |
| return 0; |
| } |
| |
| static int writeCtrlZ (ATPortId_enum port, const char *s) |
| { |
| size_t cur = 0; |
| size_t len = strlen(s); |
| ssize_t written; |
| |
| if (s_at_fd[port] < 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[port], 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[port], "\032", 1); |
| } |
| while ((written < 0 && errno == EINTR) || (written == 0)); |
| |
| if (written < 0) |
| { |
| return AT_ERROR_GENERIC; |
| } |
| |
| return 0; |
| } |
| |
| static void clearPendingCommand(ATPortId_enum port) |
| { |
| if (sp_response[port] != NULL) |
| { |
| at_response_free(sp_response[port]); |
| } |
| |
| sp_response[port] = NULL; |
| s_responsePrefix[port] = NULL; |
| s_smsPDU[port] = NULL; |
| } |
| |
| |
| /** |
| * Starts AT handler on stream "fd' |
| * returns 0 on success, -1 on error |
| */ |
| int at_open(ATPortId_enum port, int at_fd, int uart_fd, ATUnsolHandler h) |
| { |
| int ret; |
| pthread_attr_t attr; |
| |
| s_at_fd[port] = at_fd; |
| s_uart_fd[port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2] = uart_fd; |
| s_unsolHandler[port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2] = h; |
| s_readerClosed = 0; |
| s_responsePrefix[port] = NULL; |
| s_smsPDU[port] = NULL; |
| sp_response[port] = NULL; |
| |
| ATPortId_enum *at_port_ptr = (ATPortId_enum*)malloc(sizeof(ATPortId_enum)); |
| ATPortId_enum *urc_port_ptr = (ATPortId_enum*)malloc(sizeof(ATPortId_enum)); |
| *at_port_ptr = port; |
| *urc_port_ptr = port; |
| |
| pthread_attr_init (&attr); |
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
| ret = pthread_create(&s_tid_reader[port], &attr, readerLoop, at_port_ptr); |
| if (ret < 0) |
| { |
| LOGE("AT thread create fail."); |
| return -1; |
| } |
| |
| if(port == ATPORTID_SIM1_0 || port == ATPORTID_SIM2_0) { // URC only for ATPORTTYPE_0 |
| pthread_t uart_tid_reader; |
| ret = pthread_create(&uart_tid_reader, &attr, readerUrcLoop, urc_port_ptr); |
| 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(ATPortId_enum port) |
| { |
| LOGD("at_close()"); |
| if (s_at_fd[port] >= 0) |
| { |
| close(s_at_fd[port]); |
| } |
| if (s_uart_fd[port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2] >= 0) |
| { |
| close(s_uart_fd[port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2]); |
| } |
| s_at_fd[port] = -1; |
| s_uart_fd[port < ATPORTID_SIM2_0 ? MBTK_SIM_1 : MBTK_SIM_2] = -1; |
| |
| pthread_mutex_lock(&s_commandmutex[port]); |
| s_readerClosed = 1; |
| pthread_cond_signal(&s_commandcond[port]); |
| pthread_mutex_unlock(&s_commandmutex[port]); |
| /* the reader thread should eventually die */ |
| |
| at_state[port] = 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 (ATPortId_enum port, 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[port] == RIL_AT_STATE_READY) |
| at_state[port] = RIL_AT_STATE_BUSY; |
| |
| if(sp_response[port] != NULL) |
| { |
| err = AT_ERROR_COMMAND_PENDING; |
| goto error; |
| } |
| |
| err = writeline (port, command); |
| |
| if (err < 0) |
| { |
| goto error; |
| } |
| |
| s_type[port] = type; |
| s_responsePrefix[port] = responsePrefix; |
| s_smsPDU[port] = smspdu; |
| sp_response[port] = at_response_new(); |
| |
| if(timeoutMsec == 0) |
| { |
| timeoutMsec = at_timeout_get(command, &tiemout_close); |
| } |
| |
| if (timeoutMsec != 0) |
| { |
| setTimespecRelative(&ts, timeoutMsec); |
| } |
| |
| while (sp_response[port]->finalResponse == NULL && s_readerClosed == 0) |
| { |
| //LOGD("AT wait time:%lld",timeoutMsec); |
| if (timeoutMsec != 0) |
| { |
| err = pthread_cond_timedwait(&s_commandcond[port], &s_commandmutex[port], &ts); |
| } |
| else |
| { |
| err = pthread_cond_wait(&s_commandcond[port], &s_commandmutex[port]); |
| } |
| |
| //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[port]); |
| } |
| else |
| { |
| /* line reader stores intermediate responses in reverse order */ |
| reverseIntermediates(sp_response[port]); |
| *pp_outResponse = sp_response[port]; |
| } |
| |
| sp_response[port] = NULL; |
| |
| if(s_readerClosed > 0) |
| { |
| err = AT_ERROR_CHANNEL_CLOSED; |
| goto error; |
| } |
| |
| err = 0; |
| error: |
| if(at_state[port] == RIL_AT_STATE_BUSY) |
| at_state[port] = RIL_AT_STATE_READY; |
| clearPendingCommand(port); |
| |
| return err; |
| } |
| |
| /** |
| * Internal send_command implementation |
| * |
| * timeoutMsec == 0 means infinite timeout |
| */ |
| static int at_send_command_full (ATPortId_enum port, 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[port], 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[port] == RIL_AT_STATE_BUSY) |
| { |
| usleep(10000); |
| } |
| |
| pthread_mutex_lock(&s_commandmutex[port]); |
| |
| err = at_send_command_full_nolock(port, command, type, |
| responsePrefix, smspdu, |
| timeoutMsec, pp_outResponse); |
| |
| pthread_mutex_unlock(&s_commandmutex[port]); |
| |
| 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 (ATPortId_enum port, const char *command, ATResponse **pp_outResponse) |
| { |
| return at_send_command_full (port, command, NO_RESULT, NULL, |
| NULL, 0, pp_outResponse); |
| } |
| |
| |
| int at_send_command_singleline (ATPortId_enum port, const char *command, |
| const char *responsePrefix, |
| ATResponse **pp_outResponse) |
| { |
| int err; |
| |
| err = at_send_command_full (port, 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 (ATPortId_enum port, const char *command, |
| const char *responsePrefix, |
| ATResponse **pp_outResponse,long long timeoutMsec) |
| { |
| int err; |
| |
| err = at_send_command_full (port, 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 (ATPortId_enum port, const char *command, |
| ATResponse **pp_outResponse) |
| { |
| int err; |
| |
| err = at_send_command_full (port, 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 (ATPortId_enum port, const char *command, |
| const char *pdu, |
| const char *responsePrefix, |
| ATResponse **pp_outResponse) |
| { |
| int err; |
| |
| err = at_send_command_full (port, 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 (ATPortId_enum port, const char *command, |
| const char *responsePrefix, |
| ATResponse **pp_outResponse) |
| { |
| int err; |
| |
| err = at_send_command_full (port, 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(ATPortId_enum port) |
| { |
| // int i; |
| int err = 0; |
| |
| if (0 != pthread_equal(s_tid_reader[port], pthread_self())) |
| { |
| /* cannot be called from reader thread */ |
| return AT_ERROR_INVALID_THREAD; |
| } |
| pthread_mutex_lock(&s_commandmutex[port]); |
| |
| #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(port, "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[port]); |
| |
| |
| 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 == NULL) |
| { |
| return CME_ERROR_NON_CME; |
| } |
| |
| 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(ATPortId_enum port) |
| { |
| return at_state[port]; |
| } |
| |
| void at_state_set(ATPortId_enum port, mbtk_ril_at_state_enum state) |
| { |
| at_state[port] = state; |
| } |
| |
| bool at_rsp_check(ATResponse *p_response) |
| { |
| if(!p_response || !p_response->success) |
| return false; |
| |
| return true; |
| } |
| |
| void unused_func(ATPortId_enum port) |
| { |
| isFinalResponse(port, NULL); |
| } |
| |