blob: 3a69ad9bf309fa0e8b2d72c911d99c17073ec6f9 [file] [log] [blame]
/* //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);
}