blob: 1d752ed88866c55bbafc8179fd63edc6f3093df7 [file] [log] [blame]
/******************************************************************************
*(C) Copyright 2008 Marvell International Ltd.
* All Rights Reserved
******************************************************************************/
/*****************************************************************************
* Implementation for multiple clients AT commands
*
* Implemented with a Match table, where each line consists of an AT commands
* couple and a mutex. When enabled, the first AT Command which matches an entry
* in the match table will take the mutex, causing consecutive clients to sleep
* until completion.
*
*****************************************************************************/
#include <pthread.h>
#include "utlError.h"
#include "atcmd_mult.h"
#include "atcmd_mult_table.h"
#define DEBUG
#if defined(DEBUG)
#define TRACE(CAT) DBGMSG(CAT, "(%s) %s:%d: I am here\n", __func__, __FILE__, __LINE__)
#define ENTER(CAT) DBGMSG(CAT, "(%s) %s:%d: ENTER\n", __func__, __FILE__, __LINE__)
#define EXIT(CAT) DBGMSG(CAT, "(%s) %s:%d: EXIT\n", __func__, __FILE__, __LINE__)
#define LOGD(CAT, fmt, args ...) DBGMSG(CAT, "(%s) %s:%d: "fmt, __func__, __FILE__, __LINE__, ##args)
#define LOGE(CAT, fmt, args ...) ERRMSG(CAT, "(%s) %s:%d: "fmt, __func__, __FILE__, __LINE__, ##args)
#define LOGI(CAT, fmt, args ...) INFOMSG(CAT, "(%s) %s:%d: "fmt, __func__, __FILE__, __LINE__, ##args)
#else
#define TRACE()
#define LOGD(fmt, args ...)
#define LOGE(fmt, args ...)
#define LOGI(fmt, args ...)
#endif
#define ARRAY_SIZE(v) (sizeof(v) / sizeof((v)[0]))
#define MAX_PRINT_SIZE 128
/**
* struct atcmd_entry
* describes a specific AT command using name and operation
* e.g. AT+COPS=? --> name: COPS op:=?
*/
struct atcmd_entry {
char *name;
utlAtCommandType_T type;
utlAtRequestType_T op;
};
/**
* struct table entry
* consists of an at commands couple and a mutex
*/
struct table_entry {
struct atcmd_entry cmd[NUM_ENTRIES];
pthread_mutex_t *lock;
unsigned int parser_id; /* owner parser id */
};
static struct table_entry *match_table;
static ssize_t match_table_size;
/**
* atcmd2op
* returns the request utlAtRequestType_T enum.
*
* @param atcmd at command full name, e.g. +COPS=?
*
* @return request type enum
* Example:
* name - "+COPS=?"
* return value - utlAT_REQUEST_TYPE_SYNTAX
*/
static inline utlAtRequestType_T atcmd2op(const char *atcmd)
{
if (!atcmd)
return utlAT_REQUEST_TYPE_UNKNOWN;
if (strstr(atcmd, "%")) /* match any */
return utlAT_REQUEST_TYPE_ALL;
if (strstr(atcmd, "=?"))
return utlAT_REQUEST_TYPE_SYNTAX;
if (strstr(atcmd, "="))
return utlAT_REQUEST_TYPE_SET;
if (strstr(atcmd, "?"))
return utlAT_REQUEST_TYPE_GET;
return utlAT_REQUEST_TYPE_UNKNOWN;
}
/**
* atcmd2type
* get at command type enum from a give AT command string
*
* @param atcmd string containing AT command
*
* @return AT command type enum
*/
static inline utlAtCommandType_T atcmd2type(const char *atcmd)
{
if (!atcmd)
return utlAT_COMMAND_TYPE_UNKNOWN;
if (atcmd2op(atcmd) == utlAT_REQUEST_TYPE_UNKNOWN)
return utlAT_COMMAND_TYPE_BASIC;
return utlAT_COMMAND_TYPE_EXTENDED;
}
/**
* op2str
*
* @param op operation
*
* @return string representing the requested operation
*/
static inline const char *op2str(utlAtRequestType_T op)
{
switch (op) {
case utlAT_REQUEST_TYPE_GET: return "?";
case utlAT_REQUEST_TYPE_SYNTAX: return "=?";
case utlAT_REQUEST_TYPE_SET: return "=";
default: break;
}
return "#?#"; /* UNKNOWN */
}
/**
* atcmdncmp
* compare two at commands with fixed len
*
* @param e1 first at command
* @param e2 seccond at command
* @param len num of characters to compare
*
* @return 1 if e1 equals e2, 0 otherwise
*/
static inline int atcmdncmp(struct atcmd_entry *e1, struct atcmd_entry *e2,
ssize_t len)
{
if ((e1->type == e2->type) && (e1->op == e2->op))
return !strncmp(e1->name, e2->name, len);
return 0;
}
/**
* atcmdcmp
* compare two at commands
*
* @param e1 first at command
* @param e2 seccond at command
*
* @return 1 if e1 equals e2, 0 otherwise
*/
static inline int atcmdcmp(struct atcmd_entry *e1, struct atcmd_entry *e2)
{
if ((e1->type == e2->type) && (e1->op == e2->op))
return !strcmp(e1->name, e2->name);
return 0;
}
/**
* entry_match
* check for any match between two table entries
*
* @param e1 first entry
* @param e2 seccond entry
*
* @return 1 for match, 0 otherwise
*/
static inline int entry_match(struct table_entry *e1, struct table_entry *e2)
{
int i,j;
for (i = 0; i < NUM_ENTRIES; i++) {
for (j = 0; j < NUM_ENTRIES; j++) {
if (atcmdcmp(&e1->cmd[i], &e2->cmd[j]))
return 1;
}
}
return 0;
}
/**
* merge_locks
*
* Change all lines with lock2 to use lock1, and free lock2
*
* @param lock1 lock to be used for lines containing lock 2
* @param lock2
*/
static inline void merge_locks(pthread_mutex_t *lock1, pthread_mutex_t *lock2)
{
struct table_entry *e;
int i;
ASSERT(lock1);
ASSERT(lock2);
for (i = 0; i < match_table_size; i++) {
e = &match_table[i];
if (e->lock == lock2)
e->lock = lock1;
}
free(lock2);
}
/**
* match_table_init
*
* This function builds the actual match table according to the const table
* supplied in atcmd_mult_table.h.
*
* Algorithm:
*
* First run - parse all lines in the original table to at command name and op,
* and set the line lock to NULL.
*
* Second run - initialize locks which defines the logical relation between AT
* commands - at first initialize locks per line, then merge locks for lines
* sharing AT commands.
*
* Output of the algorithm is a table where each line points to a lock.
* If 2 lines points to the same lock, they are logically merged - so all
* AT commands in these lines will block each other.
*
* @return 0 for success, error code otherwise
*/
static int match_table_init(void)
{
struct table_entry *e1, *e2;
struct atcmd_entry *cmd;
const char *atcmd_str;
ssize_t i, j;
pthread_mutexattr_t attrmutex;
ENTER(match_table_init);
pthread_mutexattr_init(&attrmutex);
pthread_mutexattr_setpshared(&attrmutex, PTHREAD_PROCESS_SHARED);
match_table_size = ARRAY_SIZE(g_match_table);
match_table = malloc(match_table_size * sizeof(struct table_entry));
ASSERT(match_table);
/* Build dynamic match table - stage 1*/
for (i = 0; i < match_table_size; i++) {
match_table[i].lock = NULL;
match_table[i].parser_id = 0x0e0e0e0e;
cmd = match_table[i].cmd;
for (j = 0; j < NUM_ENTRIES; j++) {
LOGE(match_table_init1, "g_match_table[%d][%d]=%s", i, j, g_match_table[i][j]);
atcmd_str = g_match_table[i][j];
if (!atcmd_str) {
cmd[j].op = -1;
cmd[j].name = NULL;
cmd[j].type = -1;
} else {
cmd[j].op = atcmd2op(atcmd_str);
cmd[j].name = strdup(atcmd_str);
cmd[j].type = atcmd2type(atcmd_str);
}
}
}
/* Build dynamic match table - stage 2*/
for (i = 0; i < match_table_size; i++) {
e1 = &match_table[i];
ASSERT(!e1->lock);
/* try to find a matching previously initialized table entry*/
for (j = 0; j < i; j++) {
e2 = &match_table[j];
ASSERT(e2->lock);
if (entry_match(e1, e2)) {
if (!e1->lock) /* first match - use match lock*/
e1->lock = e2->lock;
else if (e1->lock != e2->lock) /* second match - merge locks */
merge_locks(e1->lock, e2->lock);
}
}
/* have we found a match? */
if (e1->lock)
continue;
/* no match found, initialize lock*/
e1->lock = malloc(sizeof(pthread_mutex_t));
ASSERT(e1->lock);
pthread_mutex_init(e1->lock, &attrmutex);
}
EXIT(match_table_init2);
return 0;
}
/**
* match_table_dump
*
* dump the built match table
*/
static void match_table_dump(void)
{
#ifdef DEBUG
struct table_entry *e;
char buf[MAX_PRINT_SIZE];
int i, j, len = 0;
memset(buf, 0, sizeof(buf));
LOGI(match_table_dump, "match table dump");
LOGI(match_table_dump1, "================");
for (i = 0; i < NUM_ENTRIES; i++) {
len += snprintf(&buf[len], MAX_PRINT_SIZE - len, " cmd%d |", i);
ASSERT(len < MAX_PRINT_SIZE);
}
LOGI(match_table_dump2, "%s mutex |", buf);
for (i = 0; i < match_table_size; i++) {
len = 0;
memset(buf, 0, sizeof(buf));
e = &match_table[i];
for (j = 0; j < NUM_ENTRIES; j++) {
len += snprintf(&buf[len], MAX_PRINT_SIZE - len, "%12s", e->cmd[j].name);
ASSERT(len < MAX_PRINT_SIZE);
}
len += snprintf(&buf[len], MAX_PRINT_SIZE - len, "%13p", e->lock);
LOGI(match_table_dump3, "%s", buf);
}
LOGI(match_table_dump4, "match table dump done");
#endif /* DEBUG */
}
/**
* match
* iterate through the match table looking for at command match
*
* @param atcmd at command name string
* @param op operation string
*
* @return table_entry pointer if a match was found, NULL otherwise
*/
static struct table_entry *match(const char *atcmd, utlAtCommandType_T type,
utlAtRequestType_T op)
{
struct table_entry *e;
ssize_t i, j;
//ENTER(match);
//LOGD(match1, "command=%s, type=%d, op=%d\n", atcmd, type, op);
if (type == utlAT_COMMAND_TYPE_EXACTION)
return NULL; /* not supported */
for (i = 0; i < match_table_size; i++) {
e = &match_table[i];
for (j = 0; j < NUM_ENTRIES; j++) {
//LOGD(match2, "entry: command=%s, type=%d, op=%d\n",
// e->cmd[j].name, e->cmd[j].type, e->cmd[j].op);
if (e->cmd[j].type != type)
continue;
if ((type == utlAT_COMMAND_TYPE_EXTENDED) &&
(e->cmd[j].op != utlAT_REQUEST_TYPE_ALL) &&
(e->cmd[j].op != op))
continue;
if (!strncmp(atcmd, e->cmd[j].name, strlen(atcmd))) {
//LOGD(match3, "MATCH found (AT%s%s)\n", atcmd,
// type == utlAT_COMMAND_TYPE_EXTENDED ?
// op2str(op) : "");
return e;
}
}
}
//EXIT(match4);
return NULL;
}
/**
* type2op
* translate from utlAtRequestType_T to utlAtParameterOp_T
*
* @param type
*
* @return
*/
static inline utlAtParameterOp_T type2op(utlAtRequestType_T type)
{
switch(type) {
case utlAT_REQUEST_TYPE_SYNTAX:
return utlAT_PARAMETER_OP_SYNTAX;
case utlAT_REQUEST_TYPE_SET:
return utlAT_PARAMETER_OP_SET;
case utlAT_REQUEST_TYPE_GET:
return utlAT_PARAMETER_OP_GET;
default:
return utlAT_PARAMETER_OP_UNKNOWN;
}
}
/**
* is_proxy
* check if given command is a proxy command
*
* @param parser
* @param cmd AT Command to check
* @param type request type
*
* @return 1 if proxy, 0 otherwise
*/
static int is_proxy(const utlAtParser_P parser, const utlAtCommand_P2c cmd)
{
unsigned int id;
utlAtParameterOp_T op;
if (!parser || !parser->call_backs.arg_p ||
!parser->call_backs.isProxyReq_function_p)
return 0;
op = (cmd->type == utlAT_COMMAND_TYPE_EXTENDED) ?
type2op(cmd->type) :
type2op(utlAT_REQUEST_TYPE_SET); /* basic command */
id = *(unsigned int*)(parser->call_backs.arg_p);
return parser->call_backs.isProxyReq_function_p(cmd->name_p, op, id);
}
/**
* atmcd_mult_lock
* Lock an at command table entry if found
*
* @param parser
* @param cmd
* @param type
*
* @return -1 for error, 0 otherwise
*/
utlReturnCode_T atmcd_mult_lock(const utlAtParser_P parser,
utlAtCommand_P2c cmd, utlAtRequestType_T type)
{
struct table_entry *e;
//ENTER(atmcd_mult_lock);
if (!parser || !parser->call_backs.arg_p || !cmd || !cmd->name_p) {
LOGE(atmcd_mult_lock1, "ERROR: Invalid params! parser=0x%x, cmd=0x%x\n", parser, cmd);
return -1;
}
if (is_proxy(parser, cmd))
return 0;
LOGD(atmcd_mult_lock2, "command=AT%s%s\n", cmd->name_p,
cmd->type == utlAT_COMMAND_TYPE_EXTENDED ? op2str(type) : "");
e = match(cmd->name_p, cmd->type, type);
if (e) {
LOGD(atmcd_mult_lock3, "LOCK [0x%x]: AT%s%s\n", e->lock, cmd->name_p,
cmd->type == utlAT_COMMAND_TYPE_EXTENDED ? op2str(type) : "");
pthread_mutex_lock(e->lock);
e->parser_id = *(unsigned int*)parser->call_backs.arg_p;
}
//EXIT(atmcd_mult_lock4);
return 0;
}
/**
* atmcd_mult_unlock
* unlock a matching command table entry if
* found
*
* @param parser
* @param cmd
* @param type
*
* @return -1 for error, 0 otherwise
*/
utlReturnCode_T atmcd_mult_unlock(const utlAtParser_P parser,
utlAtCommand_P2c cmd, utlAtRequestType_T type)
{
struct table_entry *e;
ENTER(atmcd_mult_unlock);
if (!parser || !parser->call_backs.arg_p || !cmd || !cmd->name_p) {
LOGE(atmcd_mult_unlock1, "ERROR: Invalid params! parser=0x%x, cmd=0x%x\n", parser, cmd);
return -1;
}
LOGD(atmcd_mult_unlock2, "command=AT%s%s\n", cmd->name_p,
cmd->type == utlAT_COMMAND_TYPE_EXTENDED ? op2str(type) : "");
e = match(cmd->name_p, cmd->type, type);
if (e) {
if (e->parser_id == *(unsigned int*)parser->call_backs.arg_p) {
LOGD(atmcd_mult_unlock3, "UNLOCK [0x%x]: AT%s%s\n", e->lock, cmd->name_p,
cmd->type == utlAT_COMMAND_TYPE_EXTENDED ? op2str(type) : "");
pthread_mutex_unlock(e->lock);
e->parser_id = 0x0e0e0e0e;
}
}
EXIT(atmcd_mult_unlock4);
return 0;
}
/**
* atcmd_mult_init
* initialize multiple clinets support
* functionality.
*
* @return 0 for success, error code otherwise
*/
int atcmd_mult_init(void)
{
static int init_done = 0;
ENTER(atcmd_mult_init);
if (init_done)
return 0;
match_table_init();
match_table_dump();
init_done = 1;
EXIT(atcmd_mult_init1);
return 0;
}