/******************************************************************************
*
* Copyright(c) 2019 - 2020 Realtek Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* Author: vincent_fann@realtek.com
*
*****************************************************************************/
#include "phl_headers.h"
#ifdef CONFIG_FSM
#define PHL_DEBUG_FSM
#define CLOCK_NUM 10
#define CLOCK_UNIT 10
#define IS_CLK_OFF(clk) (clk->remain < 0) /* Negative value means disabled */
#define IS_CLK_ON(clk) (clk->remain >= 0)
#define IS_CLK_EXP(clk) (clk->remain < (CLOCK_UNIT >> 1)) /* expire */
#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
#define pstr(s) (s +_os_strlen((u8 *)s))
#define lstr(s, l) (size_t)(l - _os_strlen((u8 *)s))
#define FSM_INITIAL_STATE 0
#ifdef FSM_DBG_MEM_OVERWRITE
void *fsm_kmalloc(u32 sz)
{
char *ptr;
ptr = kmalloc(sz+4, GFP_KERNEL);
memset(ptr+sz, 0xff, 4);
PHL_INFO("+AA %p %d\n", ptr, sz);
return ptr;
}
void fsm_kfree(void *ptr, u32 sz)
{
u32 ptn = 0xffffffff;
u32 *p = (u32 *)(ptr+sz);
PHL_INFO("-AA %p %d", ptr, sz);
if ((*p&ptn) != ptn) {
PHL_ERR("- %p %d", ptr, sz);
PHL_ERR("OVER WRITE %x\n", ptn);
}
kfree(ptr);
}
#define _os_kmem_alloc(a, b) fsm_kmalloc(b)
#define _os_kmem_free(a, b, c) fsm_kfree(b, c)
#endif
struct fsm_event_ent int_event_tbl[] = {
EV_ENT(FSM_INT_EV_MASK),
EV_ENT(FSM_EV_CANCEL),
EV_ENT(FSM_EV_TIMER_EXPIRE),
EV_ENT(FSM_EV_END),
EV_ENT(FSM_EV_SWITCH_IN),
EV_ENT(FSM_EV_SWITCH_OUT),
EV_ENT(FSM_EV_STATE_IN),
EV_ENT(FSM_EV_STATE_OUT),
/* Global event for announcement */
/* BE CAUREFUL the EVENT ORDER
* please also modify enum FSM_EV_ID{} in phl_fsm.h
*/
EV_ENT(FSM_GB_SCAN_START),
EV_ENT(FSM_GB_SCAN_COMPLETE),
EV_ENT(FSM_EV_MAX)
};
/*
* FSM status
*/
enum FSM_STATUS {
FSM_STATUS_NONE, /* default value */
FSM_STATUS_INITIALIZED, /* insert module ok,
* all mem/queue/timer were allocated
* has a pending thread
* phl_fsm_new_fsm()
* phl_fsm_stop_fsm()
*/
FSM_STATUS_READY, /* interface up, schedule thread, timer.
* Does NOT receive message
* phl_fsm_start_fsm()
*/
FSM_STATUS_ENABLE, /* Normal running; Reack msg
* Internal use
* fsm_enable()
*/
FSM_STATUS_DISABLE, /* Does NOT reack msg, able to receiving msg
* Internal use
* fsm_disable()
*/
};
/* @obj: obj that will be infomred to when time's up
* @counter: clock time period
* @event: event that will delivered when time's up
* @end: end time
* @pause: stop countdown
*/
struct fsm_clock {
u16 event;
void *priv;
u8 pause;
u32 start;
u32 end;
int remain; /* ms */
};
struct fsm_queue {
struct list_head q;
_os_lock lock;
};
struct fsm_main {
struct list_head list;
char name[FSM_NAME_LEN];
u8 status;
u8 obj_cnt;
u8 oid_seq; /* starts from 1 */
u8 en_clock_num;
_os_lock clock_lock;
_os_thread thread;
_os_timer fsm_timer; /* unit in ms */
struct fsm_root *root;
struct fsm_queue obj_queue;
struct fsm_queue msg_queue;
struct fsm_queue evt_queue;
_os_sema msg_ready;
bool should_stop;
/* extra custom queue; for fsm private */
struct fsm_queue ext_queue;
struct phl_info_t *phl_info; /* phl_info */
struct rtw_phl_fsm_tb tb;
};
/*
* @event: event id
* @param: additional param of the event
* @param_sz: param size
*/
struct fsm_evt {
struct list_head list;
u16 event; /* event id */
struct fsm_main *fsm;
void *param;
int param_sz;
};
/* @obj_id: object id
* @state: current state
* @prive: object's private date
*/
struct fsm_obj {
struct list_head list;
u8 oid;
u8 state;
char name[FSM_NAME_LEN];
struct fsm_clock clock[CLOCK_NUM];
struct fsm_main *fsm;
void *custom_obj;
int custom_len; /* custom obj length */
/* Global event use */
struct gbl_param my_gbl_req; /* my announcemnt to all */
struct fsm_queue gbl_queue; /* all received global events */
u16 gbl_q_len; /* number of received global event */
};
/* Main structure to handle all standalone fsm */
struct fsm_root {
_os_thread thread;
struct list_head list;
struct fsm_queue q_share_thd;
struct fsm_queue q_alone_thd;
struct phl_info_t *phl_info;
u8 gbl_seq;
_os_sema msg_ready;
u32 status; /* refer to enum FSM_ROOT_STATUS_FLAGS */
};
/* Static function porto type */
static int fsm_handler(struct fsm_main *fsm);
static char *fsm_state_name(struct fsm_main *fsm, u8 state);
static u8 fsm_get_evt_level(struct fsm_main *fsm, u16 event);
static void fsm_status_set(struct fsm_main *fsm, enum FSM_STATUS status)
{
fsm->status = status;
}
static enum FSM_STATUS fsm_status(struct fsm_main *fsm)
{
return fsm->status;
}
/* unit ms */
u32 phl_fsm_time_pass(u32 start)
{
u32 now = _os_get_cur_time_ms();
u32 pass;
if (start <= now)
pass = now - start;
else
pass = 0xffffffff - start + now;
return pass;
}
u32 phl_fsm_time_left(u32 start, u32 end)
{
u32 total, pass;
int left = 0;
pass = phl_fsm_time_pass(start);
if (end >= start)
total = end - start;
else
total = 0xffffffff - start + end;
left = total - pass;
if (left < 0)
left = 0;
return (u32)left;
}
#if 0
static struct fsm_main *fsm_dequeue_fsm(struct fsm_root *root, u8 fsm_mode)
{
void *d = phl_to_drvpriv(root->phl_info);
struct fsm_main *fsm;
struct fsm_queue *queue = (fsm_mode == FSM_ALONE_THREAD) ?
(&root->q_alone_thd) : (&root->q_share_thd);
if (list_empty(&queue->q))
return NULL;
_os_spinlock(d, &queue->lock, _bh, NULL);
fsm = list_first_entry(&queue->q, struct fsm_main, list);
list_del(&fsm->list);
_os_spinunlock(d, &queue->lock, _bh, NULL);
return fsm;
}
static struct fsm_obj *fsm_dequeue_obj(struct fsm_main *fsm)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_obj *obj;
if (list_empty(&fsm->obj_queue.q))
return NULL;
_os_spinlock(d, &fsm->obj_queue.lock, _bh, NULL);
obj = list_first_entry(&fsm->obj_queue.q, struct fsm_obj, list);
list_del(&obj->list);
_os_spinunlock(d, &fsm->obj_queue.lock, _bh, NULL);
return obj;
}
#endif
static struct fsm_msg *fsm_dequeue_msg(struct fsm_main *fsm)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_msg *msg;
if (list_empty(&fsm->msg_queue.q))
return NULL;
_os_spinlock(d, &fsm->msg_queue.lock, _bh, NULL);
msg = list_first_entry(&fsm->msg_queue.q, struct fsm_msg, list);
list_del(&msg->list);
_os_spinunlock(d, &fsm->msg_queue.lock, _bh, NULL);
return msg;
}
static struct fsm_evt *fsm_dequeue_evt(struct fsm_main *fsm)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_evt *evt;
if (list_empty(&fsm->evt_queue.q))
return NULL;
_os_spinlock(d, &fsm->evt_queue.lock, _bh, NULL);
evt = list_first_entry(&fsm->evt_queue.q, struct fsm_evt, list);
list_del(&evt->list);
_os_spinunlock(d, &fsm->evt_queue.lock, _bh, NULL);
return evt;
}
/* For EXTERNAL application to enqueue message to extra queue (expose)
*
* @fsm: fsm that object belonged to
* @msg: message to be enqueued
* @to_head: enqueue message to the head
*/
int phl_fsm_enqueue_ext(struct fsm_main *fsm, struct fsm_msg *msg, u8 to_head)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_queue *queue = &fsm->ext_queue;
_os_spinlock(d, &queue->lock, _bh, NULL);
if (to_head)
list_add(&msg->list, &queue->q);
else
list_add_tail(&msg->list, &queue->q);
_os_spinunlock(d, &queue->lock, _bh, NULL);
return 0;
}
/* For EXTERNAL application to dequeue message from extra queue (expose)
*
* @fsm: fsm that object belonged to
*/
struct fsm_msg *phl_fsm_dequeue_ext(struct fsm_main *fsm)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_msg *msg;
if (list_empty(&fsm->ext_queue.q))
return NULL;
_os_spinlock(d, &fsm->ext_queue.lock, _bh, NULL);
msg = list_first_entry(&fsm->ext_queue.q, struct fsm_msg, list);
list_del(&msg->list);
_os_spinunlock(d, &fsm->ext_queue.lock, _bh, NULL);
return msg;
}
/* For EXTERNAL application to dequeue message from extra queue (expose)
*
* @fsm: fsm that object belonged to
*/
int phl_fsm_is_ext_queue_empty(struct fsm_main *fsm)
{
return list_empty(&fsm->ext_queue.q);
}
static int fsm_new_oid(struct fsm_main *fsm)
{
u8 oid = fsm->oid_seq++;
if (fsm->oid_seq == 0xFF) {
PHL_WARN("%s: reach MAX object ID 0x%x\n",
fsm->name, oid);
}
return oid;
}
static int fsm_enqueue_list(void *d, struct fsm_main *fsm,
struct fsm_queue *queue, struct list_head *list)
{
_os_spinlock(d, &queue->lock, _bh, NULL);
list_add_tail(list, &queue->q);
_os_spinunlock(d, &queue->lock, _bh, NULL);
return 0;
}
static enum fsm_run_rtn fsm_state_run(struct fsm_obj *obj,
u16 event, void *param)
{
struct fsm_main *fsm = obj->fsm;
/* TODO protect incorrect event */
FSM_EV_MSG(fsm, fsm_get_evt_level(fsm, event),
"%s-%d %-18s %s\n", fsm->name, obj->oid,
fsm_state_name(fsm, obj->state), phl_fsm_evt_name(obj, event));
return fsm->tb.state_tbl[obj->state].fsm_func(obj->custom_obj,
event, param);
}
static void fsm_remove_all_queuing_msg(struct fsm_main *fsm)
{
struct fsm_msg *msg;
struct fsm_evt *evt;
void *d = phl_to_drvpriv(fsm->phl_info);
/* go through msg queue and free everything */
while ((msg = fsm_dequeue_msg(fsm)) != NULL) {
if (msg->param)
_os_kmem_free(d, (void *)msg->param, msg->param_sz);
_os_kmem_free(d, (void *)msg, sizeof(*msg));
}
/* go through event queue and free everything */
while ((evt = fsm_dequeue_evt(fsm)) != NULL) {
if (evt->param)
_os_kmem_free(d, (void *)evt->param, evt->param_sz);
_os_kmem_free(d, (void *)evt, sizeof(*evt));
}
/* go through ext queue and free everything */
while ((msg = phl_fsm_dequeue_ext(fsm)) != NULL) {
if (msg->param)
_os_kmem_free(d, (void *)msg->param, msg->param_sz);
_os_kmem_free(d, (void *)msg, sizeof(*msg));
}
}
static int fsm_cancel_all_running_obj(struct fsm_main *fsm)
{
struct fsm_obj *obj;
phl_list_for_loop(obj, struct fsm_obj, &fsm->obj_queue.q, list) {
phl_fsm_gen_msg(fsm->phl_info, obj, NULL, 0, FSM_EV_CANCEL);
}
return 0;
}
u8 phl_fsm_dbg_level(struct fsm_main *fsm, u8 level)
{
if (fsm->tb.dbg_level >= level)
return fsm->tb.dbg_level;
return 0;
}
u8 phl_fsm_evt_level(struct fsm_main *fsm, u8 level)
{
if (fsm->tb.evt_level >= level)
return fsm->tb.evt_level;
return 0;
}
static u8 fsm_get_evt_level(struct fsm_main *fsm, u16 event)
{
u16 ev;
/* fsm internal event */
if (event & FSM_INT_EV_MASK) {
ev = (u8)(event & ~(FSM_EV_MASK));
return int_event_tbl[ev].evt_level;
}
if (event == FSM_EV_UNKNOWN)
return FSM_DBG_INFO;
if (event > fsm->tb.max_event)
return FSM_DBG_INFO;
/* user event */
return fsm->tb.evt_tbl[event].evt_level;
}
static void fsm_init_queue(void *d, struct fsm_queue *queue)
{
INIT_LIST_HEAD(&queue->q);
_os_spinlock_init(d, &queue->lock);
}
static void fsm_deinit_queue(void *d, struct fsm_queue *queue)
{
_os_spinlock_free(d, &queue->lock);
}
/* For External obj to check sould stop status
*
* @fsm: fsm to get state
*/
bool phl_fsm_should_stop(struct fsm_main *fsm)
{
return fsm->should_stop;
}
int fsm_thread_share(void *param)
{
struct fsm_main *fsm, *fsm_t;
struct fsm_root *root = (struct fsm_root *)param;
void *d = phl_to_drvpriv(root->phl_info);
while (1) {
_os_sema_down(d, &root->msg_ready);
if (_os_thread_check_stop(d, &(root->thread)))
break;
phl_list_for_loop_safe(fsm, fsm_t,
struct fsm_main, &root->q_share_thd.q, list) {
if (fsm_status(fsm) == FSM_STATUS_ENABLE)
fsm_handler(fsm);
}
}
_os_thread_wait_stop(d, &root->thread);
PHL_INFO("fsm: [root] thread down\n");
return 0;
}
int fsm_thread_alone(void *param)
{
struct fsm_main *fsm = (struct fsm_main *)param;
void *d = phl_to_drvpriv(fsm->phl_info);
while (1) {
_os_sema_down(d, &fsm->msg_ready);
if (_os_thread_check_stop(d, &(fsm->thread)))
break;
if (fsm_status(fsm) == FSM_STATUS_ENABLE)
fsm_handler(fsm);
}
_os_thread_wait_stop(d, &fsm->thread);
FSM_INFO(fsm, "fsm: [%s] thread down\n", fsm->name);
return 0;
}
static struct fsm_obj *fsm_get_obj(struct fsm_main *fsm, u8 oid)
{
struct fsm_obj *obj, *obj_t;
void *d = phl_to_drvpriv(fsm->phl_info);
_os_spinlock(d, &fsm->obj_queue.lock, _bh, NULL);
phl_list_for_loop_safe(obj, obj_t,
struct fsm_obj, &fsm->obj_queue.q, list) {
if (oid == (obj->oid)) {
_os_spinunlock(d, &fsm->obj_queue.lock, _bh, NULL);
return obj;
}
}
_os_spinunlock(d, &fsm->obj_queue.lock, _bh, NULL);
return NULL;
}
struct fsm_msg *phl_fsm_new_msg(struct fsm_obj *obj, u16 event)
{
#ifdef PHL_INCLUDE_FSM
struct fsm_msg *msg = NULL;
void *d = phl_to_drvpriv(obj->fsm->phl_info);
if (fsm_status(obj->fsm) != FSM_STATUS_ENABLE) {
PHL_ERR("%s: is out of service, ignore message %s!\n",
obj->fsm->name, phl_fsm_evt_name(obj, event));
return NULL;
}
msg = (struct fsm_msg *)_os_kmem_alloc(d, sizeof(*msg));
if (msg == NULL)
return NULL;
_os_mem_set(d, msg, 0, sizeof(*msg));
msg->event = event;
if (obj) {
msg->fsm = obj->fsm;
msg->oid = obj->oid;
}
return msg;
#else
PHL_WARN("fsm: %s exclude FSM\n", __func__);
return NULL;
#endif
}
enum rtw_phl_status phl_fsm_sent_msg(struct fsm_obj *obj, struct fsm_msg *msg)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
if (fsm_status(obj->fsm) != FSM_STATUS_ENABLE) {
PHL_ERR("fsm: %s is out of service, ignore message %s!\n",
obj->fsm->name, phl_fsm_evt_name(obj, msg->event));
return RTW_PHL_STATUS_RESOURCE;
}
fsm_enqueue_list(d, obj->fsm, &obj->fsm->msg_queue, &msg->list);
if (obj->fsm->tb.mode == FSM_ALONE_THREAD)
_os_sema_up(d, &obj->fsm->msg_ready);
else
_os_sema_up(d, &obj->fsm->root->msg_ready);
return RTW_PHL_STATUS_SUCCESS;
}
static struct fsm_msg *fsm_new_timer_msg(struct fsm_obj *obj,
u16 event, void *priv)
{
struct fsm_msg *msg = NULL;
void *d = phl_to_drvpriv(obj->fsm->phl_info);
msg = (struct fsm_msg *)_os_kmem_alloc(d, sizeof(*msg));
if (msg == NULL)
return msg;
_os_mem_set(d, msg, 0, sizeof(*msg));
msg->event = event;
msg->oid = obj->oid;
msg->param = priv;
return msg;
}
static int fsm_post_message(struct fsm_obj *obj, u16 event, void *priv)
{
struct fsm_msg *msg;
struct fsm_main *fsm = obj->fsm;
void *d = phl_to_drvpriv(obj->fsm->phl_info);
msg = fsm_new_timer_msg(obj, event, priv);
if (msg == NULL)
return -1;
fsm_enqueue_list(d, fsm, &fsm->msg_queue, &msg->list);
if (obj->fsm->tb.mode == FSM_ALONE_THREAD)
_os_sema_up(d, &fsm->msg_ready);
else
_os_sema_up(d, &fsm->root->msg_ready);
return 0;
}
void fsm_timer_callback(void *context)
{
struct fsm_main *fsm = (struct fsm_main *)context;
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_obj *obj;
struct fsm_clock *clk;
int i;
_os_set_timer(d, &fsm->fsm_timer, CLOCK_UNIT);
if (fsm->en_clock_num == 0)
return;
/* go through clock and descrease timer
* if timer was expired, issue event
*/
phl_list_for_loop(obj, struct fsm_obj, &fsm->obj_queue.q, list) {
_os_spinlock(d, &obj->fsm->clock_lock, _bh, NULL);
for (i = 0; i < CLOCK_NUM; i++) {
clk = &obj->clock[i];
if (IS_CLK_OFF(clk) || clk->pause)
continue;
clk->remain = (int)phl_fsm_time_left(clk->start,
clk->end);
//(clk->remain < 0 ) ? 0 : clk->remain;
/* timer expired */
if (!IS_CLK_EXP(clk))
continue;
#ifdef PHL_DBG_FSM
FSM_DBG(obj->fsm, "%s: expire in %d ms\n",
phl_fsm_evt_name(obj, clk->event),
phl_fsm_time_pass(clk->start));
#endif
clk->end = 0;
clk->remain = -1;
/* send message to obj */
/* check fsm status before posting */
if (fsm_status(fsm) != FSM_STATUS_INITIALIZED &&
fsm_status(fsm) != FSM_STATUS_DISABLE)
fsm_post_message(obj, clk->event, clk->priv);
fsm->en_clock_num--;
}
_os_spinunlock(d, &obj->fsm->clock_lock, _bh, NULL);
}
}
/* allocate and init fsm resource */
struct fsm_main *phl_fsm_init_fsm(struct fsm_root *root, const char *name,
void *priv, struct rtw_phl_fsm_tb *tb)
{
#ifdef PHL_INCLUDE_FSM
struct fsm_main *fsm;
struct phl_info_t *phl_info = (struct phl_info_t *)priv;
void *d = phl_to_drvpriv(phl_info);
//char name_t[FSM_NAME_LEN+10];
/* check event table */
if (tb->evt_tbl[tb->max_event-1].event != tb->max_event-1) {
PHL_ERR("Event mismatch ? Is max event = %d != %d ?\n",
tb->evt_tbl[tb->max_event-1].event,
tb->max_event-1);
return NULL;
}
/* check state table */
if (tb->state_tbl[tb->max_state-1].state != tb->max_state-1) {
PHL_ERR("State mismatch ? Is max state = %d != %d) ?\n",
tb->state_tbl[tb->max_state-1].state,
tb->max_state-1);
return NULL;
}
fsm = (struct fsm_main *)_os_kmem_alloc(d, sizeof(*fsm));
if (fsm == NULL)
return NULL;
_os_mem_set(d, fsm, 0, sizeof(*fsm));
_os_mem_cpy(d, &fsm->tb, (void *)tb, sizeof(*tb));
_os_mem_cpy(d, &fsm->name, (void *)name,
MIN(FSM_NAME_LEN-1, _os_strlen((u8 *)name)));
fsm->root = root;
fsm->phl_info = phl_info;
fsm_init_queue(d, &(fsm->obj_queue));
fsm_init_queue(d, &(fsm->msg_queue));
fsm_init_queue(d, &(fsm->evt_queue));
fsm_init_queue(d, &(fsm->ext_queue));
_os_spinlock_init(d, &fsm->clock_lock);
_os_init_timer(d, &fsm->fsm_timer, fsm_timer_callback, fsm, "fsm");
fsm->oid_seq = 1;
/* link fsm_main to fsm_root */
if (tb->mode == FSM_ALONE_THREAD) {
_os_sema_init(d, &fsm->msg_ready, 0);
fsm_enqueue_list(d, fsm, &root->q_alone_thd, &fsm->list);
} else
fsm_enqueue_list(d, fsm, &root->q_share_thd, &fsm->list);
FSM_INFO(fsm, "fsm: [%s] initialized\n", fsm->name);
fsm_status_set(fsm, FSM_STATUS_INITIALIZED);
return fsm;
#else
PHL_WARN("fsm: %s exclude FSM\n", __func__);
return NULL;
#endif /* PHL_INCLUDE_FSM */
}
/* For EXTERNAL application to deinit fsm (expose)
* @fsm: see struct fsm_main
*/
enum rtw_phl_status phl_fsm_deinit_fsm(struct fsm_main *fsm)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_obj *obj, *obj_t;
_os_release_timer(d, &fsm->fsm_timer);
/* remove fsm form link list */
list_del(&fsm->list);
phl_list_for_loop_safe(obj, obj_t,
struct fsm_obj, &fsm->obj_queue.q, list) {
list_del(&obj->list);
phl_fsm_flush_gbl(obj);
fsm_deinit_queue(d, &(obj->gbl_queue));
/* free custom_obj */
_os_kmem_free(d, obj->custom_obj, obj->custom_len);
/* free fsm_obj */
_os_kmem_free(d, obj, sizeof(*obj));
}
fsm_deinit_queue(d, &(fsm->obj_queue));
fsm_deinit_queue(d, &(fsm->msg_queue));
fsm_deinit_queue(d, &(fsm->evt_queue));
fsm_deinit_queue(d, &(fsm->ext_queue));
_os_spinlock_free(d, &fsm->clock_lock);
if (fsm->tb.mode == FSM_ALONE_THREAD)
_os_sema_free(d, &fsm->msg_ready);
FSM_INFO(fsm, "fsm: [%s] uninitilized\n", fsm->name);
_os_kmem_free(d, fsm, sizeof(*fsm));
return RTW_PHL_STATUS_SUCCESS;
}
char *phl_fsm_evt_name(struct fsm_obj *obj, u16 event)
{
struct fsm_main *fsm = obj->fsm;
u8 ev;
/* TODO handle global, internal, user event */
/* global event */
if (event & FSM_GBL_EV_MASK)
return "global";
/* fsm internal event */
if (event & FSM_INT_EV_MASK) {
ev = (u8)(event & ~(FSM_EV_MASK));
return int_event_tbl[ev].name;
}
if (event == FSM_EV_UNKNOWN)
return "FSM_EV_UNKNOWN";
if (event > fsm->tb.max_event)
return "undefine";
/* user event */
return fsm->tb.evt_tbl[event].name;
}
static char *fsm_state_name(struct fsm_main *fsm, u8 state)
{
if (state > fsm->tb.max_state)
return "unknown";
return fsm->tb.state_tbl[state].name;
}
/* For EXTERNAL application to get state id (expose)
*
* @obj: obj to get state
*/
u8 phl_fsm_state_id(struct fsm_obj *obj)
{
return obj->state;
}
/** init obj internal variable
*
* @fsm: fsm that object belonged to
* default init to the 1st state in state_tbl
*/
static void fsm_obj_switch_in(struct fsm_obj *obj)
{
struct fsm_main *fsm = obj->fsm;
//void *d = phl_to_drvpriv(fsm->phl_info);
/* default init to the 1st state in state_tbl */
obj->state = fsm->tb.state_tbl[0].state;
FSM_INFO(fsm, "%s-%d %-18s -> %s\n", fsm->name, obj->oid,
"switch in", fsm_state_name(fsm, obj->state));
/* make it alive! Hello OBJ! */
fsm_state_run(obj, FSM_EV_SWITCH_IN, NULL);
}
/** deinit obj internal variable
*
* @fsm: fsm that object belonged to
* default init to the 1st state in state_tbl
*/
static void fsm_obj_switch_out(struct fsm_obj *obj)
{
struct fsm_main *fsm = obj->fsm;
//void *d = phl_to_drvpriv(fsm->phl_info);
/* default init to the 1st state in state_tbl */
obj->state = fsm->tb.state_tbl[0].state;
FSM_INFO(fsm, "%s-%d %-18s -> %s\n", fsm->name, obj->oid,
"switch out", fsm_state_name(fsm, obj->state));
/* make it alive! Hello OBJ! */
fsm_state_run(obj, FSM_EV_SWITCH_OUT, NULL);
}
/* For EXTERNAL application to new a fsm object (expose)
*
* @fsm: fsm that object belonged to
* @fsm_obj: obj param when calling FSM framework function
* @priv_len: custom obj length
*
* return value: NULL :fail
* other :cusomer obj handler (success)
*/
void *phl_fsm_new_obj(struct fsm_main *fsm,
void **fsm_obj, int sz)
{
#ifdef PHL_INCLUDE_FSM
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_obj *obj;
int i;
obj = (struct fsm_obj *)_os_kmem_alloc(d, sizeof(*obj));
if (obj == NULL)
return NULL;
_os_mem_set(d, obj, 0, sizeof(*obj));
obj->custom_obj = _os_kmem_alloc(d, sz);
if (obj->custom_obj == NULL) {
_os_kmem_free(d, obj, sizeof(*obj));
return NULL;
}
_os_mem_set(d, obj->custom_obj, 0, sz);
for (i = 0; i < CLOCK_NUM; i++)
obj->clock[i].remain = -1; /* Negative means disable */
fsm_init_queue(d, &(obj->gbl_queue));
obj->custom_len = sz;
obj->oid = (u8)fsm_new_oid(fsm);
obj->fsm = fsm;
_os_mem_set(d, obj->name, 0, FSM_NAME_LEN);
_os_snprintf(obj->name, FSM_NAME_LEN,
"%s-%d", obj->fsm->name, obj->oid);
*fsm_obj = obj;
fsm_enqueue_list(d, fsm, &fsm->obj_queue, &obj->list);
return obj->custom_obj;
#else
PHL_WARN("fsm: %s exclude FSM\n", __func__);
return NULL;
#endif /* PHL_INCLUDE_FSM */
}
/* For EXTERNAL application to destory a fsm object (expose)
*
* @fsm_obj: obj param when calling FSM framework function
*/
void phl_fsm_destory_obj(struct fsm_obj *obj)
{
struct fsm_main *fsm = obj->fsm;
void *d = phl_to_drvpriv(fsm->phl_info);
list_del(&obj->list);
phl_fsm_flush_gbl(obj);
fsm_deinit_queue(d, &(obj->gbl_queue));
/* free custom_obj */
_os_kmem_free(d, obj->custom_obj, obj->custom_len);
/* free fsm_obj */
_os_kmem_free(d, obj, sizeof(*obj));
}
bool phl_fsm_is_alarm_off_ext(struct fsm_obj *obj, u8 id)
{
struct fsm_clock *clock = &obj->clock[id];
return IS_CLK_OFF(clock);
}
bool phl_fsm_is_alarm_off(struct fsm_obj *obj)
{
struct fsm_clock *clock = &obj->clock[0];
return IS_CLK_OFF(clock);
}
static void fsm_set_alarm(struct fsm_obj *obj, int ms,
u16 event, u8 id, void *priv)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
struct fsm_clock *clock = &obj->clock[id];
u32 now;
if (ms == 0)
fsm_post_message(obj, event, priv);
_os_spinlock(d, &obj->fsm->clock_lock, _bh, NULL);
/* turn on clock from off */
if (IS_CLK_OFF(clock))
obj->fsm->en_clock_num++;
now = _os_get_cur_time_ms();
clock->event = event;
clock->priv = priv;
clock->start = now;
clock->end = now + ms;
clock->remain = (int)phl_fsm_time_left(clock->start, clock->end);
_os_spinunlock(d, &obj->fsm->clock_lock, _bh, NULL);
#ifdef PHL_DBG_FSM
FSM_DBG(obj->fsm, "%s:%s now=0x%08x, end=0x%08x, remain=0x%08x\n",
phl_fsm_obj_name(obj), phl_fsm_evt_name(obj, event),
clock->start, clock->end, clock->remain);
#endif
}
/* For EXTERNAL application to extend alarm time (expose)
*
* @obj: obj param when calling FSM framework function
* @event: alarm will issue this event while timer expired
* @ms: time period for the alarm
* remain time does not less than 'ms'
* @id: alarm id; start from 1
*/
void phl_fsm_extend_alarm_ext(struct fsm_obj *obj, int ms, u8 id)
{
struct fsm_clock *clk = &obj->clock[id];
int remain = ms;
if (id == 0 || id >= CLOCK_NUM) {
PHL_ERR("%s: %s_%d fail\n",
phl_fsm_obj_name(obj), __func__, id);
return;
}
if (IS_CLK_OFF(clk))
return;
remain = MAX((int)phl_fsm_time_left(clk->start, clk->end), ms);
phl_fsm_set_alarm_ext(obj, remain, clk->event, id, clk->priv);
}
/* For EXTERNAL application to setup alarm (expose)
*
* @obj: obj param when calling FSM framework function
* @event: alarm will issue this event while timer expired
* @ms: time period for the alarm
* @id: alarm id; start from 1
*/
void phl_fsm_set_alarm(struct fsm_obj *obj, int ms, u16 event)
{
fsm_set_alarm(obj, ms, event, 0, NULL);
}
/* For EXTERNAL application to setup alarm_ext (expose)
*
* @obj: obj param when calling FSM framework function
* @event: alarm will issue this event while timer expired
* @ms: time period for the alarm
* @id: alarm id; start from 1
* @priv: priv from caller
*/
void phl_fsm_set_alarm_ext(struct fsm_obj *obj,
int ms, u16 event, u8 id, void *priv)
{
if (id >= CLOCK_NUM) {
PHL_ERR("%s: set alarm_ext_%d to %d ms fail\n",
phl_fsm_obj_name(obj), id, ms);
return;
}
fsm_set_alarm(obj, ms, event, id, priv);
}
static void fsm_cancel_alarm(struct fsm_obj *obj, u8 id)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
struct fsm_clock *clock = &obj->clock[id];
_os_spinlock(d, &obj->fsm->clock_lock, _bh, NULL);
/* turn off clock from on */
if (IS_CLK_ON(clock))
obj->fsm->en_clock_num--;
//obj->clock[id].counter = -1;
obj->clock[id].end = 0;
obj->clock[id].remain = -1;
obj->clock[id].pause = 0;
_os_spinunlock(d, &obj->fsm->clock_lock, _bh, NULL);
}
/* For EXTERNAL application to cancel alarm (expose)
*
* @obj: obj param when calling FSM framework function
*/
void phl_fsm_cancel_alarm(struct fsm_obj *obj)
{
fsm_cancel_alarm(obj, 0);
}
/* For EXTERNAL application to cancel alarm_ext (expose)
*
* @obj: obj param when calling FSM framework function
* @id: alarm id; start from 1
*/
void phl_fsm_cancel_alarm_ext(struct fsm_obj *obj, u8 id)
{
if (id == 0 || id >= CLOCK_NUM) {
PHL_ERR("%s: cancel alarm_ext_%d fail\n",
phl_fsm_obj_name(obj), id);
return;
}
fsm_cancel_alarm(obj, id);
}
static void fsm_pause_alarm(struct fsm_obj *obj, u8 id)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
_os_spinlock(d, &obj->fsm->clock_lock, _bh, NULL);
obj->clock[id].pause = 1;
_os_spinunlock(d, &obj->fsm->clock_lock, _bh, NULL);
}
/* For EXTERNAL application to pause alarm (expose)
*
* @obj: obj param when calling FSM framework function
*/
void phl_fsm_pause_alarm(struct fsm_obj *obj)
{
fsm_pause_alarm(obj, 0);
}
/* For EXTERNAL application to pause alarm_ext (expose)
*
* @obj: obj param when calling FSM framework function
* @id: alarm id; start from 1
*/
void phl_fsm_pause_alarm_ext(struct fsm_obj *obj, u8 id)
{
if (id == 0 || id >= CLOCK_NUM) {
PHL_ERR("%s: pause alarm_%d fail\n", phl_fsm_obj_name(obj), id);
return;
}
fsm_pause_alarm(obj, id);
}
static void fsm_resume_alarm(struct fsm_obj *obj, u8 id)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
u32 cur = _os_get_cur_time_ms();
/* extrend end time */
_os_spinlock(d, &obj->fsm->clock_lock, _bh, NULL);
obj->clock[id].end = cur + obj->clock[id].remain;
obj->clock[id].pause = 0;
_os_spinunlock(d, &obj->fsm->clock_lock, _bh, NULL);
}
/* For EXTERNAL application to resume alarm (expose)
*
* @obj: obj param when calling FSM framework function
*/
void phl_fsm_resume_alarm(struct fsm_obj *obj)
{
fsm_resume_alarm(obj, 0);
}
/* For EXTERNAL application to resume alarm_ext (expose)
*
* @obj: obj param when calling FSM framework function
* @id: alarm id; start from 1
*/
void phl_fsm_resume_alarm_ext(struct fsm_obj *obj, u8 id)
{
if (id == 0 || id >= CLOCK_NUM) {
PHL_ERR("%s: resume alarm_ext_%d fail\n",
phl_fsm_obj_name(obj), id);
return;
}
fsm_resume_alarm(obj, id);
}
/* For EXTERNAL application to change state (expose)
*
* @obj: obj that changes state
* @new_state: new state
*/
void phl_fsm_state_goto(struct fsm_obj *obj, u8 new_state)
{
struct fsm_main *fsm = NULL;
if (obj->state == new_state)
return;
fsm = obj->fsm;
fsm_state_run(obj, FSM_EV_STATE_OUT, NULL);
FSM_MSG(fsm, FSM_DBG_DBG, "\n");
FSM_MSG(fsm, FSM_DBG_DBG, "%s-%d %-18s -> %s\n", fsm->name, obj->oid,
fsm_state_name(fsm, obj->state),
fsm_state_name(fsm, new_state));
obj->state = new_state; /* new state */
fsm_state_run(obj, FSM_EV_STATE_IN, NULL);
}
static void fsm_user_evt_handler(struct fsm_main *fsm)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_msg *msg;
struct fsm_obj *obj;
int rtn = FSM_FREE_PARAM;
while ((msg = fsm_dequeue_msg(fsm)) != NULL) {
rtn = FSM_FREE_PARAM;
obj = fsm_get_obj(fsm, msg->oid);
if (obj == NULL) {
PHL_WARN("%s-%d: obj not found\n",
fsm->name, msg->oid);
goto obj_not_found;
}
/* DO NOT deliver event when fsm->should_stop is true */
if ((fsm->should_stop == true) &&
(obj->state == FSM_INITIAL_STATE) &&
(msg->event < FSM_INT_EV_MASK)) {
PHL_INFO("%s: should stop skip msg %s\n",
phl_fsm_obj_name(obj),
phl_fsm_evt_name(obj, msg->event));
goto skip_msg;
}
/* run state machine */
rtn = fsm_state_run(obj, msg->event, msg->param);
skip_msg:
obj_not_found:
if ((rtn == FSM_FREE_PARAM) &&
(msg->param_sz > 0) &&
(msg->param != NULL))
_os_kmem_free(d, (void *)msg->param, msg->param_sz);
_os_kmem_free(d, (void *)msg, sizeof(*msg));
}
}
static int fsm_update_status(struct fsm_main *fsm)
{
struct fsm_obj *obj;
phl_list_for_loop(obj, struct fsm_obj, &fsm->obj_queue.q, list) {
if (obj->state != FSM_INITIAL_STATE) {
PHL_INFO("%s: state %s\n",
phl_fsm_obj_name(obj),
fsm_state_name(fsm, obj->state));
return 0;
}
}
/* all objs are at INITAL_STATE
* fsm module is ready to stop
*/
fsm_status_set(fsm, FSM_STATUS_INITIALIZED);
return 0;
}
static int fsm_handler(struct fsm_main *fsm)
{
/* USER EVENT */
fsm_user_evt_handler(fsm);
if (fsm->should_stop == true)
fsm_update_status(fsm);
return 0;
}
/* For EXTERNAL application to get fsm name (expose)
* @fsm: fsm to be get name
*/
char *phl_fsm_fsm_name(struct fsm_main *fsm)
{
return fsm->name;
}
/* For EXTERNAL application to get obj name (expose)
* @obj: obj to be get name
* For example: scan-1 (sacn obj with object id 1)
*/
char *phl_fsm_obj_name(struct fsm_obj *obj)
{
return obj->name;
}
/* For EXTERNAL application to cancel sma (expose)
* @obj: obj job will be cancelled
*/
enum rtw_phl_status phl_fsm_cancel_obj(struct fsm_obj *obj)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
struct fsm_msg *msg;
int rtn;
/* NEW message to cancel obj task */
msg = phl_fsm_new_msg(obj, FSM_EV_CANCEL);
if (msg == NULL) {
PHL_ERR("%s: alloc msg fail\n", obj->fsm->name);
return RTW_PHL_STATUS_RESOURCE;
}
rtn = phl_fsm_sent_msg(obj, msg);
if (rtn != RTW_PHL_STATUS_SUCCESS)
_os_kmem_free(d, msg, sizeof(*msg));
return rtn;
}
/* For EXTERNAL application to init FSM framework (expose) */
/* @obj: obj job will be cancelled
*/
struct fsm_root *phl_fsm_init_root(void *priv)
{
#ifdef PHL_INCLUDE_FSM
struct fsm_root *root;
struct phl_info_t *phl_info = (struct phl_info_t *)priv;
void *d = phl_to_drvpriv(phl_info);
int max, size;
/* check size of internal event table */
max = FSM_EV_MAX & ~(int_event_tbl[0].event);
size = sizeof(int_event_tbl)/sizeof(int_event_tbl)[0];
if (size != max + 1) {
PHL_ERR("fsm: int_event_tbl[%d] != %d size mismatch!!",
size, max);
return NULL;
}
root = (struct fsm_root *)_os_kmem_alloc(d, sizeof(*root));
if (root == NULL)
return NULL;
_os_mem_set(d, root, 0, sizeof(*root));
fsm_init_queue(d, &(root->q_share_thd));
fsm_init_queue(d, &(root->q_alone_thd));
_os_sema_init(d, &root->msg_ready, 0);
root->phl_info = phl_info;
PHL_INFO("fsm: [root] initialized\n");
return root;
#else
PHL_WARN("fsm: %s exclude FSM\n", __func__);
return 0;
#endif /* PHL_INCLUDE_FSM */
}
/* For EXTERNAL application to deinit FSM framework (expose)
* @root: FSM framework handler
*/
void phl_fsm_deinit_root(struct fsm_root *root)
{
#ifdef PHL_INCLUDE_FSM
void *d = phl_to_drvpriv(root->phl_info);
void *c = NULL;
fsm_deinit_queue(d, &(root->q_alone_thd));
fsm_deinit_queue(d, &(root->q_share_thd));
_os_sema_free(d, &root->msg_ready);
/* free fsm_root */
_os_kmem_free(d, root, sizeof(*root));
FSM_INFO(c, "fsm: [root] uninitilized\n");
#else
PHL_WARN("fsm: %s exclude FSM\n", __func__);
#endif /* PHL_INCLUDE_FSM */
}
/* For EXTERNAL application to start fsm root (expose)
* @fsm: see struct fsm_main
*/
enum rtw_phl_status phl_fsm_start_root(struct fsm_root *root)
{
void *d = phl_to_drvpriv(root->phl_info);
#ifdef CONFIG_LINUX_THREAD
root->thread = kthread_create(fsm_thread_share, root,
"fsm_thread_share");
wake_up_process(root->thread);
#else
_os_thread_init(d, &(root->thread), fsm_thread_share, root,
"fsm_thread_share");
_os_thread_schedule(d, &(root->thread));
#endif
return RTW_PHL_STATUS_SUCCESS;
}
/* For EXTERNAL application to stop fsm root (expose)
* @fsm: see struct fsm_main
*/
enum rtw_phl_status phl_fsm_stop_root(struct fsm_root *root)
{
void *d = phl_to_drvpriv(root->phl_info);
void *c = NULL;
_os_thread_stop(d, &(root->thread));
_os_sema_up(d, &root->msg_ready);
_os_thread_deinit(d, &(root->thread));
FSM_INFO(c, "fsm: [root] stopped\n");
return RTW_PHL_STATUS_SUCCESS;
}
/* For EXTERNAL application to start fsm (expose)
* @fsm: see struct fsm_main
*/
enum rtw_phl_status phl_fsm_start_fsm(struct fsm_main *fsm)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_obj *obj;
phl_list_for_loop(obj, struct fsm_obj, &fsm->obj_queue.q, list) {
fsm_obj_switch_in(obj);
}
if (fsm->tb.mode == FSM_ALONE_THREAD) {
_os_thread_init(d, &(fsm->thread), fsm_thread_alone, fsm,
"fsm_thread_alone");
_os_thread_schedule(d, &(fsm->thread));
}
_os_set_timer(d, &fsm->fsm_timer, CLOCK_UNIT);
fsm->status = FSM_STATUS_READY;
fsm_status_set(fsm, FSM_STATUS_ENABLE);
FSM_INFO(fsm, "fsm: [%s] started\n", fsm->name);
return RTW_PHL_STATUS_SUCCESS;
}
#define WAIT_DUR 10
#define WAIT_TIMES 20
/* For EXTERNAL application to stop fsm (expose)
* @fsm: see struct fsm_main
*/
enum rtw_phl_status phl_fsm_stop_fsm(struct fsm_main *fsm)
{
void *d = phl_to_drvpriv(fsm->phl_info);
struct fsm_obj *obj;
int wait = WAIT_TIMES;
fsm->should_stop = true;
/* CANCEL all objs within fsm */
fsm_cancel_all_running_obj(fsm);
/* wait fsm module finish its task elegantly */
while ((fsm->status != FSM_STATUS_INITIALIZED) && --wait)
_os_sleep_ms(d, WAIT_DUR);
if (wait < (WAIT_TIMES >> 1))
FSM_INFO(fsm, "%s: take %dms to disable\n",
fsm->name, (WAIT_TIMES-wait)*WAIT_DUR);
fsm_status_set(fsm, FSM_STATUS_DISABLE);
_os_spinlock(d, &fsm->clock_lock, _bh, NULL);
_os_cancel_timer(d, &fsm->fsm_timer);
_os_spinunlock(d, &fsm->clock_lock, _bh, NULL);
phl_list_for_loop(obj, struct fsm_obj, &fsm->obj_queue.q, list) {
fsm_obj_switch_out(obj);
phl_fsm_flush_gbl(obj);
}
fsm_remove_all_queuing_msg(fsm);
if (fsm->tb.mode == FSM_ALONE_THREAD) {
_os_thread_stop(d, &(fsm->thread));
_os_sema_up(d, &fsm->msg_ready);
_os_thread_deinit(d, &(fsm->thread));
}
fsm->should_stop = false;
FSM_INFO(fsm, "fsm: [%s] stopped\n", fsm->name);
return RTW_PHL_STATUS_SUCCESS;
}
/* For EXTERNAL application to generate message buffer (expose)
* Generate message quickly and simply
* @phl: phl_info_t
* @obj: fsm_obj (msg receiver)
* @pbuf: message parameter
* @sz: message parameter size
* @event: event for the message
*/
enum rtw_phl_status phl_fsm_gen_msg(void *phl, struct fsm_obj *obj,
void *pbuf, u32 sz, u16 event)
{
#ifdef PHL_INCLUDE_FSM
struct phl_info_t *phl_info = (struct phl_info_t *)phl;
struct fsm_msg *msg;
void *d = phl_to_drvpriv(phl_info);
void *param = NULL;
int rtn = RTW_PHL_STATUS_RESOURCE;
/* NEW mem for message */
msg = phl_fsm_new_msg(obj, event);
if (msg == NULL) {
FSM_ERR(obj->fsm, "%s: alloc msg %s fail\n",
phl_fsm_obj_name(obj),
phl_fsm_evt_name(obj, event));
goto msg_fail;
}
/* NEW mem for param */
if (pbuf && sz) {
param = _os_kmem_alloc(d, sz);
if (param == NULL) {
FSM_ERR(obj->fsm,
"%s: alloc param %s fail\n",
phl_fsm_obj_name(obj),
phl_fsm_evt_name(obj, event));
goto param_fail;
}
_os_mem_cpy(d, param, pbuf, sz);
}
msg->param = (void *)param;
msg->param_sz = sz;
rtn = phl_fsm_sent_msg(obj, msg);
if (rtn != RTW_PHL_STATUS_SUCCESS)
goto send_fail;
return rtn;
send_fail:
if (msg->param && msg->param_sz)
_os_kmem_free(d, msg->param, msg->param_sz);
param_fail:
_os_kmem_free(d, msg, sizeof(*msg));
msg_fail:
return rtn;
#else
PHL_WARN("fsm: %s exclude FSM\n", __func__);
return RTW_PHL_STATUS_FAILURE;
#endif /* PHL_INCLUDE_FSM */
}
enum rtw_phl_status phl_fsm_flush_gbl(struct fsm_obj *obj)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
struct gbl_param *p;
_os_mem_set(d, &obj->my_gbl_req, 0, sizeof(obj->my_gbl_req));
/* flush obj->gbl_queue */
phl_list_for_loop(p,
struct gbl_param, &obj->gbl_queue.q, list) {
list_del(&p->list);
FSM_WARN(obj->fsm, "%s: del non replied %s:%s #%d\n",
phl_fsm_obj_name(obj),
phl_fsm_obj_name(p->obj_from),
phl_fsm_evt_name(obj, p->event), p->seq);
_os_kmem_free(d, (void *)p, sizeof(*p));
}
obj->gbl_q_len = 0;
return RTW_PHL_STATUS_SUCCESS;
}
/* For EXTERNAL fsm module to announce global msg (expose)
*
* !!! ONLY ALLOW fsm MODULE to call !!!
* !!! Otherwise will have reaing issue !!!
*
* Global msg will go throughs all fsm modules
* Limitation:
* Only supports ONE Glboal announcement at a time
* The latest one always overwrite previous one
*
* reference: phl_fsm_gbl_not_reply_num()
*
* @obj: fsm_obj
* @gbl_evt: Global event to be announced
* @cb_evt: call back event when things was done
* return: wait time(ms); 0: success, waiting is not necessary
* when wait > 0; callee will reply event to caller within ms
* negative value: fail
*/
int phl_fsm_gbl_msg_announce(struct fsm_obj *obj, u16 gbl_evt, u16 cb_evt)
{
struct fsm_root *root = obj->fsm->root;
void *d = phl_to_drvpriv(root->phl_info);
struct fsm_main *fsm = obj->fsm;
struct fsm_main *fsm_t;
struct fsm_obj *obj_t;
int i;
if (obj->my_gbl_req.count > 0) {
/* Should not happen!!
* Have ongoing announcement
* We are waiting for some GBL event reply
*/
for (i = 0; i < PHL_FSM_MAX_WAIT_OCUNT; i++) {
if (obj->my_gbl_req.wait_list[i])
FSM_WARN(fsm,
"%s: drop not replied %s:%s #%d\n",
phl_fsm_obj_name(obj),
phl_fsm_obj_name(
obj->my_gbl_req.wait_list[i]),
phl_fsm_evt_name(obj,
obj->my_gbl_req.event),
obj->my_gbl_req.seq);
}
}
/* create param for announcement */
_os_mem_set(d, &obj->my_gbl_req, 0, sizeof(obj->my_gbl_req));
obj->my_gbl_req.event = gbl_evt;
obj->my_gbl_req.cb_evt = cb_evt;
obj->my_gbl_req.obj_from = obj;
if (obj->fsm->root->gbl_seq == 0) /* 0 reserved */
obj->fsm->root->gbl_seq = 1;
obj->my_gbl_req.seq = obj->fsm->root->gbl_seq++;
/* GLOBAL EVENT will go through all fsms */
phl_list_for_loop(fsm_t, struct fsm_main, &root->q_share_thd.q, list) {
if (fsm_status(fsm_t) != FSM_STATUS_ENABLE) {
FSM_INFO(fsm_t, "fsm: [%s] disabled, skip %s\n",
phl_fsm_fsm_name(fsm_t),
phl_fsm_evt_name(obj, gbl_evt));
continue;
}
/* go through objs */
phl_list_for_loop(obj_t, struct fsm_obj,
&fsm_t->obj_queue.q, list) {
/* skip myself */
if (obj_t == obj)
continue;
fsm_state_run(obj_t, gbl_evt, &obj->my_gbl_req);
if (obj->my_gbl_req.result < 0) {
FSM_ERR(fsm_t,
"%s: announce %s to %s fail(%d)\n",
phl_fsm_obj_name(obj),
phl_fsm_evt_name(obj_t, gbl_evt),
phl_fsm_obj_name(obj_t),
obj->my_gbl_req.result);
return obj->my_gbl_req.result;
}
}
}
return obj->my_gbl_req.wait_ms;
}
/** For GBL announcer to get the number of un-replied fsm (espose)
*
* !!! ONLY ALLOW fsm MODULE to call !!!
*
* reference: phl_fsm_gbl_msg_announce()
* @obj: fsm_obj
* @param: see gbl_param
* return: 0 means there is no non-reply reqest, it's ready to go;
* otherwise yet ready
*/
int phl_fsm_gbl_not_reply_num(struct fsm_obj *obj, struct gbl_param *param)
{
if (param == NULL)
return obj->my_gbl_req.count;
/* we don't have any waitting reply; GBL may be cancelled earlier */
if (obj->my_gbl_req.obj_from == NULL) {
FSM_WARN(obj->fsm, "%s: doesn't expect reply %s:%s #%d\n",
phl_fsm_obj_name(obj),
phl_fsm_obj_name(param->obj_to),
phl_fsm_evt_name(obj, param->event), param->seq);
return -1;
}
/* Are we looking for receiving event ? */
if (param->event != obj->my_gbl_req.event)
return -2;
if (param->seq != obj->my_gbl_req.seq)
return -3;
FSM_INFO(obj->fsm, "%s: got reply %s:%s #%d\n",
phl_fsm_obj_name(obj),
phl_fsm_obj_name(param->obj_to),
phl_fsm_evt_name(obj, param->event), param->seq);
/* clear incoming reporter from waitting list */
param->wait_list[param->count] = NULL;
return --obj->my_gbl_req.count;
}
/** For Global event reciver to inform announcer to wait confirmation (espose)
*
* !!! ONLY ALLOW fsm MODULE to call !!!
*
* Call the function if Global receiver know that it can't finish task in time
* Global event receiver expect FSM_EV_GBL_REPLY to confirm task is finish
* reference : phl_fsm_gbl_msg_release()
*
* @obj: see fsm_obj
* @param: see gbl_param
* @ms: How long(max) can finish task according to received Global event
* caller will set an alarm to react if we can't finish the job on time
* return: negative value : fail
* postive value : seq number of this GBL event
*/
int phl_fsm_gbl_msg_hold(struct fsm_obj *obj,
struct gbl_param *param, u32 ms)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
struct gbl_param *p;
if (param->count >= PHL_FSM_MAX_WAIT_OCUNT) {
param->result = -(GBL_ST_WAIT_REACH_MAX);
FSM_ERR(obj->fsm, "%s: hold %s reach max counter %d (%d)",
phl_fsm_obj_name(obj),
phl_fsm_evt_name(obj, param->event), param->count,
param->result);
return param->result;
}
if (obj->gbl_q_len >= PHL_FSM_MAX_WAIT_OCUNT) {
param->result = -(GBL_ST_REPLY_REACH_MAX);
FSM_ERR(obj->fsm, "%s: reply %s reach max counter %d (%d)",
phl_fsm_obj_name(obj),
phl_fsm_evt_name(obj, param->event),
obj->gbl_q_len, param->result);
return param->result;
}
p = (struct gbl_param *)_os_kmem_alloc(d, sizeof(*p));
if (p == NULL) {
param->result = -GBL_ST_ALLOC_MEM_FAIL;
FSM_ERR(obj->fsm, "%s: reply %s, alloc mem fail (%d)",
phl_fsm_obj_name(obj),
phl_fsm_evt_name(obj, param->event),
param->result);
return param->result;
}
/* fill info to inform caller that we need time to process */
param->obj_to = obj;
param->wait_list[param->count] = obj;
param->wait_ms = MAX(param->wait_ms, ms);
param->count++;
/* save param for replying later */
_os_mem_cpy(d, p, (void *)param, sizeof(*param));
fsm_enqueue_list(d, obj->fsm, &obj->gbl_queue, &p->list);
FSM_DBG(obj->fsm, "%s: require %d ms to handle %s:%s #%d\n",
phl_fsm_obj_name(obj), ms,
phl_fsm_obj_name(param->obj_from),
phl_fsm_evt_name(obj, param->event),
param->seq);
return p->seq;
}
/** For Global event reciver to inform announcer that task was done (espose)
*
* !!! ONLY ALLOW fsm MODULE to call !!!
*
* Call the function when Global receiver finish the task
* This is a ASYNC confirmation to Global event announcer
* Global event announcer will receive FSM_EV_GBL_REPLY when function is called
* reference: phl_fsm_gbl_msg_hold()
*
* @obj: see fsm_obj
* @param: see gbl_param
* @obj: see fsm_obj
* @event: event to be replied
* @seq: event to be replied
* @result: result to be replied
*/
enum rtw_phl_status phl_fsm_gbl_msg_release(struct fsm_obj *obj,
u16 event, u32 seq, enum gbl_evt_result result)
{
void *d = phl_to_drvpriv(obj->fsm->phl_info);
struct gbl_param *p, *p_t;
/* handle multiple Global event requests
* go through link list to get reply param according to event
*/
phl_list_for_loop_safe(p, p_t,
struct gbl_param, &obj->gbl_queue.q, list) {
if ((event == p->event) && (seq == p->seq)) {
p->result = result;
FSM_INFO(obj->fsm, "%s: reply %s:%s #%d, result %d\n",
phl_fsm_obj_name(obj),
phl_fsm_obj_name(p->obj_from),
phl_fsm_evt_name(obj, event), p->seq, result);
phl_fsm_gen_msg(obj->fsm->phl_info, p->obj_from,
p, sizeof(*p), p->cb_evt);
list_del(&p->list);
_os_kmem_free(d, (void *)p, sizeof(*p));
break;
}
}
return RTW_PHL_STATUS_SUCCESS;
}
/** Debug funcitons
*
*/
#ifdef PHL_DEBUG_FSM
static void fsm_dbg_dump_fsm_queue(struct fsm_queue *fsmq,
char *s, int *sz,bool detail)
{
struct fsm_main *fsm, *fsm_t;
char *ptr = s;
int len = *sz;
phl_list_for_loop_safe(fsm, fsm_t,
struct fsm_main, &fsmq->q, list) {
_os_snprintf(pstr(ptr), lstr(ptr, len), "\t%4s : %s\n", fsm->name,
fsm->tb.mode ? "STANDALONE":"SHARE");
if (fsm->tb.dump_fsm && detail) {
len = lstr(ptr, len);
ptr = pstr(ptr);
fsm->tb.dump_fsm(fsm, ptr, &len);
}
}
*sz = len;
}
static void fsm_dbg_help(struct fsm_main *fsm, char *s, int *sz, bool detail);
static void fsm_dbg_dump_fsm(struct fsm_main *fsm,
char *s, int *sz, bool detail)
{
int len = *sz;
char *ptr = s;
_os_snprintf(pstr(ptr), lstr(ptr, len), "\t%4s : %s\n", fsm->name,
fsm->tb.mode ? "STANDALONE":"SHARE");
if (fsm->tb.dump_fsm && detail) {
len = lstr(ptr, len);
ptr = pstr(ptr);
fsm->tb.dump_fsm(fsm, ptr, &len);
}
}
static void fsm_dbg_dump_state(struct fsm_main *fsm,
char *s, int *sz, bool detail)
{
int i;
int len = *sz;
_os_snprintf(pstr(s), lstr(s, len),
"[%s] state table\n", fsm->name);
for (i = 0; i < fsm->tb.max_state; i++)
_os_snprintf(pstr(s), lstr(s, len), "\t%4d : %s\n",
i, fsm->tb.state_tbl[i].name);
*sz = len;
}
static void fsm_dbg_dump_event(struct fsm_main *fsm,
char *s, int *sz, bool detail)
{
int i, max;
int len = *sz;
/* internal event */
_os_snprintf(pstr(s), lstr(s, len), "[Internal] event table\n");
max = FSM_EV_END & ~(int_event_tbl[0].event); /* FSM_INT_EV_MASK */
for (i = 1; i < max; i++)
_os_snprintf(pstr(s), lstr(s, len), "\t0x%4x : %s\n",
int_event_tbl[i].event, int_event_tbl[i].name);
/* user event */
_os_snprintf(pstr(s), lstr(s, len), "\n[%s] event table max %d\n", fsm->name, fsm->tb.max_event);
for (i = 0; i < fsm->tb.max_event-1; i++)
_os_snprintf(pstr(s), lstr(s, len), "\t0x%4x : %s\n",
fsm->tb.evt_tbl[i].event, fsm->tb.evt_tbl[i].name);
*sz = len;
}
static void fsm_dbg_dump_obj(struct fsm_main *fsm,
char *s, int *sz, bool detail)
{
struct fsm_obj *obj, *obj_t;
int len = *sz;
char *ptr = s;
phl_list_for_loop_safe(obj, obj_t,
struct fsm_obj, &fsm->obj_queue.q, list) {
_os_snprintf(pstr(ptr), lstr(ptr, len), "%s-%d : state %s",
fsm->name, obj->oid, fsm_state_name(fsm, obj->state));
if (fsm->tb.dump_obj && detail) {
len = lstr(ptr, len);
ptr = pstr(ptr);
fsm->tb.dump_obj(obj->custom_obj, ptr, &len);
}
}
*sz = len;
}
static void fsm_dbg_max(struct fsm_main *fsm, char *s, int *sz, bool detail)
{
int len = *sz;
_os_snprintf(pstr(s), lstr(s, len),
"ERR: fsm %s sould not run to here!!\n", __func__);
*sz = len;
}
struct fsm_debug_ent {
char *opt;
void (*func)(struct fsm_main *fsm, char *s, int *sz, bool detail);
char *desc;
};
struct fsm_debug_ent debug_opt[] = {
{"help", fsm_dbg_help, "help message"},
{"fsm", fsm_dbg_dump_fsm, "all fsm name"},
{"st", fsm_dbg_dump_state, "state name"},
{"ev", fsm_dbg_dump_event, "event name"},
{"obj", fsm_dbg_dump_obj, "obj detail"},
{"max", fsm_dbg_max, "max_opt"}
};
static void _fsm_dbg_help(struct fsm_root *root, char *s, int *sz, bool detail)
{
int i, max_opt;
int len = *sz;
char *ptr = s;
_os_snprintf(pstr(ptr), lstr(ptr, len),
"usage:\tfsm d <fsm_name> <option>\n");
_os_snprintf(pstr(ptr), lstr(ptr, len),
"\tfsm p,<obj_name> <priv_dbg_cmd> ....\n");
_os_snprintf(pstr(ptr), lstr(ptr, len),
"\tfsm s,<obj_name> <EVENT>\n");
_os_snprintf(pstr(ptr), lstr(ptr, len),
"\tfsm w,<fsm_name> <dbg_level|ev_level> <0-5(dbg)>\n");
_os_snprintf(pstr(s), lstr(ptr, len), "\nfsm_name:\n");
len = lstr(ptr, len);
ptr = pstr(ptr);
fsm_dbg_dump_fsm_queue(&root->q_share_thd, ptr, &len, detail);
len = lstr(ptr, len);
ptr = pstr(ptr);
fsm_dbg_dump_fsm_queue(&root->q_alone_thd, ptr, &len, detail);
_os_snprintf(pstr(ptr), lstr(ptr, len), "\noption:\n");
max_opt = sizeof(debug_opt)/sizeof(debug_opt[0]);
for (i = 0; i < max_opt-1; i++)
_os_snprintf(pstr(ptr), lstr(ptr, len), "%12s : %s\n",
debug_opt[i].opt, debug_opt[i].desc);
*sz = len;
}
static void fsm_dbg_help(struct fsm_main *fsm, char *s, int *sz, bool detail)
{
_fsm_dbg_help(fsm->root, s, sz, false);
}
struct fsm_main *get_fsm_by_name(struct fsm_root *root, char *name)
{
void *d = phl_to_drvpriv(root->phl_info);
struct fsm_main *fsm, *fsm_t;
u32 len = _os_strlen((u8 *)name);
if (len > FSM_NAME_LEN)
return NULL;
phl_list_for_loop_safe(fsm, fsm_t,
struct fsm_main, &root->q_share_thd.q, list) {
if (_os_strlen((u8 *)fsm->name) == len &&
_os_mem_cmp(d, fsm->name, name, len) == 0)
return fsm;
}
phl_list_for_loop_safe(fsm, fsm_t,
struct fsm_main, &root->q_alone_thd.q, list) {
if (_os_strlen((u8 *)fsm->name) == len &&
_os_mem_cmp(d, fsm->name, name, len) == 0)
return fsm;
}
return NULL;
}
static u16 fsm_get_evt_id(struct fsm_main *fsm, char *event)
{
void *d = phl_to_drvpriv(fsm->phl_info);
int i;
u32 len = _os_strlen((u8 *)event);
/* internal event */
for (i = 0; i < (sizeof(int_event_tbl)/sizeof(int_event_tbl[0])); i++) {
if (_os_strlen((u8 *)int_event_tbl[i].name) == len &&
_os_mem_cmp(d, int_event_tbl[i].name, event, len) == 0)
return int_event_tbl[i].event;
}
/* user event */
for (i = 0; i < fsm->tb.max_event; i++) {
if (_os_strlen((u8 *)fsm->tb.evt_tbl[i].name) == len &&
_os_mem_cmp(d,
fsm->tb.evt_tbl[i].name, event, len) == 0)
return fsm->tb.evt_tbl[i].event;
}
return FSM_EV_UNKNOWN;
}
#endif /* PHL_DEBUG_FSM */
/* For EXTERNAL application to debug fsm (expose)
* @phl_info: phl main struct
* @input: input cmd
* @input_num: num of cmd param
* @output: output buffer
* @out_len: MAX output buffer len
*
* d: dump fsm info
* fsm <d> <fsm_name> <fsm|st|ev|obj>
* p: private cmd to fsm module
* fsm <p> <obj_name> <cmd to fsm module>
* s: send event to fsm
* fsm <s> <obj_name> <ev>
* w: write debug level
* fsm <w> <fsm_name> <dbg_level|evt_level> <0-5>
*/
void phl_fsm_dbg(struct phl_info_t *phl_info, char input[][MAX_ARGV],
u32 input_num, char *output, u32 out_len)
{
#ifdef PHL_DEBUG_FSM
struct phl_info_t *phl = (struct phl_info_t *)phl_info;
void *d = phl_to_drvpriv(phl);
struct fsm_root *root = phl->fsm_root;
struct fsm_main *fsm = NULL;
struct fsm_obj *obj = NULL;
struct fsm_msg *msg;
int i, max_opt, len = out_len;
char fsm_name[FSM_NAME_LEN], opt[FSM_NAME_LEN], cmd[FSM_NAME_LEN];
char c, *ptr, *sp;
u8 obj_id = 0;
u16 ev_id;
ptr = output;
/* fsm <cmd> <fsm_name> <opt> : fsm d cmd ev
* fsm <cmd> <fsm_name> <evt> : fsm s cmd-1 FSM_EV_CANCEL
*/
if (input_num < 4)
goto help;
_os_mem_set(d, cmd, 0, FSM_NAME_LEN);
_os_mem_cpy(d, cmd, input[1],
MIN(_os_strlen((u8 *)input[1]), FSM_NAME_LEN));
_os_mem_set(d, fsm_name, 0, FSM_NAME_LEN);
_os_mem_cpy(d, fsm_name, input[2],
MIN(_os_strlen((u8 *)input[2]), FSM_NAME_LEN));
_os_mem_set(d, opt, 0, FSM_NAME_LEN);
_os_mem_cpy(d, opt, input[3],
MIN(_os_strlen((u8 *)input[3]), FSM_NAME_LEN));
c = (char)*cmd;
/* read obj_id
* if fsm_name is "cmd-1" then obj number is "1"
*/
sp = _os_strchr((const char *)fsm_name, '-');
if (sp != NULL) {
*sp = '\0';
if (_os_sscanf(sp+1, "%hhd", &obj_id) != 1) {
_os_snprintf(pstr(ptr), lstr(ptr, len),
"ERR: fsm[%s] miss obj_id\n", fsm_name);
return;
}
} else
obj_id = 1; /* assume obj-1 */
/* search fsm by name */
fsm = get_fsm_by_name(root, (char *)fsm_name);
if (fsm == NULL) {
_os_snprintf(pstr(ptr), lstr(ptr, len),
"ERR: fsm[%s] not found\n", fsm_name);
return;
}
obj = fsm_get_obj(fsm, obj_id);
if (obj == NULL) {
_os_snprintf(pstr(ptr), lstr(ptr, len),
"ERR: fsm[%s] miss obj_%d\n", fsm_name, obj_id);
return;
}
switch (c) {
case 'd':
/* dump status */
max_opt = sizeof(debug_opt)/sizeof(debug_opt)[0];
for (i = 0; i < max_opt-1; i++) {
if (_os_strlen((u8 *)debug_opt[i].opt) == \
_os_strlen((u8 *)opt) &&
_os_mem_cmp(d, debug_opt[i].opt, opt,
_os_strlen((u8 *)opt)) == 0) {
len = lstr(ptr, len);
ptr = pstr(ptr);
debug_opt[i].func(fsm, ptr, &len, true);
break;
}
}
break;
case 'p':
/* call fsm private degug function */
if ((fsm != NULL) && (obj != NULL) && (fsm->tb.debug != NULL)){
len = lstr(ptr, len);
ptr = pstr(ptr);
fsm->tb.debug(obj->custom_obj, &input[3],
(input_num - 3), ptr, (u32 *)&len);
}
break;
case 's':
/* get event id */
ev_id = fsm_get_evt_id(fsm, (char *)opt);
if (ev_id == FSM_EV_UNKNOWN) {
_os_snprintf(pstr(ptr), lstr(ptr, len),
"\n\nERR: fsm[%s] unknown event %s\n",
fsm_name, opt);
len = lstr(ptr, len);
ptr = pstr(ptr);
fsm_dbg_dump_event(fsm, ptr, &len, false);
break;
}
if (obj != NULL) {
msg = phl_fsm_new_msg(obj, ev_id);
/* send event */
if (phl_fsm_sent_msg(obj, msg) != RTW_PHL_STATUS_SUCCESS)
_os_kmem_free(d, msg, sizeof(*msg));
}
break;
case 'w':
/* write cfg */
/* fsm w,<fsm_name>,<dbg_level|ev_level>,<0-5(dbg)> */
sp = _os_strchr((const char *)opt, ',');
if (sp == NULL)
goto help;
*sp = '\0';
if (_os_sscanf(sp+1, "%d", &i) != 1)
goto help;
if ((i<0) || (i>5))
goto help;
if (!_os_strcmp(opt, "dbg_level")) {
fsm->tb.dbg_level = (u8)i;
_os_snprintf(pstr(ptr), lstr(ptr, len),
"\n%s: set debug level to %d\n",
phl_fsm_fsm_name(fsm), i);
} else if (!_os_strcmp(opt, "evt_level")) {
_os_snprintf(pstr(ptr), lstr(ptr, len),
"\n%s: set event level to %d\n",
phl_fsm_fsm_name(fsm), i);
//fsm->tb.evt_level = (u8)i;
} else
goto help;
break;
default:
goto help;
}
return;
help:
len = lstr(ptr, len);
ptr = pstr(ptr);
_fsm_dbg_help(fsm->root, ptr, &len, false);
#endif /* PHL_DEBUG_FSM */
}
#endif /*CONFIG_FSM*/