/******************************************************************************
 *
 * 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*/
