| /****************************************************************************** |
| *(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; |
| } |