blob: 0894dc6b3562c3ecb2b30af969355a6cc0e928e5 [file] [log] [blame]
/*
* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/**
* DOC: Implements general SM framework
*/
#include "wlan_sm_engine.h"
#include "wlan_sm_engine_dbg.h"
#include <qdf_module.h>
#include <qdf_mem.h>
#include <qdf_str.h>
QDF_STATUS wlan_sm_dispatch(struct wlan_sm *sm, uint16_t event,
uint16_t event_data_len, void *event_data)
{
bool event_handled = false;
uint8_t state;
const char *event_name = NULL;
if (!sm) {
sm_engine_err("SM is NULL");
return QDF_STATUS_E_FAILURE;
}
state = sm->cur_state;
if (event == WLAN_SM_ENGINE_EVENT_NONE) {
sm_engine_err("%s: invalid event %d", sm->name, event);
return QDF_STATUS_E_FAILURE;
}
sm->last_event = event;
wlan_sm_save_history(sm, SM_EVENT_MSG_PROCESSING, sm->cur_state,
sm->cur_state, event);
if (sm->event_names) {
if (event < sm->num_event_names)
event_name = sm->event_names[event];
sm_engine_nofl_info("%s: current state %s[%d] event %s[%d]",
sm->name, sm->state_info[state].name, state,
event_name ? event_name : "UNKNOWN_EVENT",
event);
} else {
sm_engine_nofl_info("%s: current state %s[%d] event %d",
sm->name,
sm->state_info[state].name, state,
event);
}
if (state != WLAN_SM_ENGINE_STATE_NONE) {
event_handled = (*sm->state_info[state].wlan_sm_event) (
sm->ctx, event, event_data_len, event_data);
if (!event_handled) {
sm_engine_nofl_err("%s: event %d not handled in state %s",
sm->name, event,
sm->state_info[sm->cur_state].name);
return QDF_STATUS_E_INVAL;
}
}
return QDF_STATUS_SUCCESS;
}
qdf_export_symbol(wlan_sm_dispatch);
void wlan_sm_transition_to(struct wlan_sm *sm, uint8_t state)
{
const struct wlan_sm_state_info *state_info = sm->state_info;
uint8_t new_state;
uint8_t old_state;
uint8_t new_sub_st;
uint8_t ol_sub_st;
uint8_t cur_state;
if (!sm) {
sm_engine_err("SM is NULL");
return;
}
cur_state = sm->cur_state;
/* cannot change state from state entry/exit routines */
if (qdf_atomic_read(&sm->in_state_transition)) {
sm_engine_alert(
"%s: can not call state transition from entry/exit routines",
sm->name);
QDF_BUG(0);
return;
}
qdf_atomic_set(&sm->in_state_transition, 1);
wlan_sm_save_history(sm, SM_EVENT_STATE_TRANSITION, sm->cur_state,
state, 0xFF);
if ((state == WLAN_SM_ENGINE_STATE_NONE) ||
(state >= WLAN_SM_ENGINE_MAX_STATES) ||
(state >= sm->num_states)) {
sm_engine_err(
"%s: to state %d needs to be a valid state current_state=%d",
sm->name, cur_state, state);
return;
}
/*
* Here state and sub state are derived for debug printing only
* as SME keeps state and sub state as flat, to differentiate between
* state and substate, checks current state if it has parent state,
* the parent state is printed along with the sub state
*/
if (state_info[cur_state].parent_state != WLAN_SM_ENGINE_STATE_NONE)
old_state = state_info[cur_state].parent_state;
else
old_state = cur_state;
if (state_info[state].parent_state != WLAN_SM_ENGINE_STATE_NONE)
new_state = state_info[state].parent_state;
else
new_state = state;
if (state_info[cur_state].parent_state != WLAN_SM_ENGINE_STATE_NONE)
ol_sub_st = cur_state;
else
ol_sub_st = 0;
if (state_info[state].parent_state != WLAN_SM_ENGINE_STATE_NONE)
new_sub_st = state;
else
new_sub_st = 0;
sm_engine_nofl_info("%s: transition(state) %s[%d] => %s[%d]",
sm->name, state_info[old_state].name, old_state,
state_info[new_state].name, new_state);
sm_engine_nofl_info("%s: transition(sub_state) %s[%d] => %s[%d]",
sm->name,
ol_sub_st ? state_info[ol_sub_st].name : "IDLE",
ol_sub_st,
new_sub_st ? state_info[new_sub_st].name : "IDLE",
new_sub_st);
/*
* call the exit function(s) of the current state hierarchy
* starting from substate.
*/
while (cur_state != WLAN_SM_ENGINE_STATE_NONE) {
if (state_info[cur_state].wlan_sm_exit)
state_info[cur_state].wlan_sm_exit(sm->ctx);
cur_state = state_info[cur_state].parent_state;
}
/*
* call the entry function(s) of the current state hierarchy
* starting from superstate.
*/
cur_state = state;
while (cur_state != WLAN_SM_ENGINE_STATE_NONE) {
if (state_info[cur_state].wlan_sm_entry)
state_info[cur_state].wlan_sm_entry(sm->ctx);
sm->cur_state = cur_state;
cur_state = state_info[cur_state].initial_substate;
if (cur_state != WLAN_SM_ENGINE_STATE_NONE)
sm_engine_debug("%s: Entering Initial sub state %s",
sm->name, state_info[cur_state].name);
}
qdf_atomic_set(&sm->in_state_transition, 0);
}
qdf_export_symbol(wlan_sm_transition_to);
void wlan_sm_reset(struct wlan_sm *sm, uint8_t init_state)
{
sm->cur_state = init_state;
}
static QDF_STATUS wlan_sm_validate_state_info(const char *name,
const struct wlan_sm_state_info *state_info,
uint8_t i)
{
bool state_visited[WLAN_SM_ENGINE_MAX_STATES] = {false};
uint8_t state, next_state;
/*
* make sure that the state definitions are in order
*/
if ((state_info[i].state >= WLAN_SM_ENGINE_MAX_STATES) ||
(state_info[i].state != i)) {
sm_engine_err("%s: entry %d has invalid state %d",
name, i, state_info[i].state);
return QDF_STATUS_E_FAILURE;
}
/* detect if there is any loop in the hierarichy */
state = state_info[i].state;
while (state != WLAN_SM_ENGINE_STATE_NONE) {
if (state_visited[state]) {
sm_engine_err("%s: detected a loop with entry %d",
name, i);
return QDF_STATUS_E_FAILURE;
}
state_visited[state] = true;
next_state = state_info[state].parent_state;
if (next_state != WLAN_SM_ENGINE_STATE_NONE) {
if (!state_info[next_state].has_substates) {
sm_engine_err(
"%s: state %d is marked as parent of %d but is not a super state",
name, next_state, state);
return QDF_STATUS_E_FAILURE;
}
}
state = next_state;
}
return QDF_STATUS_SUCCESS;
}
struct wlan_sm *wlan_sm_create(const char *name, void *ctx,
uint8_t init_state,
struct wlan_sm_state_info *state_info,
uint8_t num_states,
const char **event_names,
uint32_t num_event_names)
{
struct wlan_sm *sm;
u_int32_t i;
if (num_states > WLAN_SM_ENGINE_MAX_STATES) {
sm_engine_err("%s: Num states exceeded", name);
return NULL;
}
/*
* validate the state_info table.
* the entries need to be valid and also
* need to be in order.
*/
for (i = 0; i < num_states; ++i) {
if (wlan_sm_validate_state_info(name, state_info, i) !=
QDF_STATUS_SUCCESS) {
sm_engine_err("%s: states validation failed", name);
return NULL;
}
}
sm = qdf_mem_malloc(sizeof(*sm));
if (!sm) {
sm_engine_alert("%s: sm allocation failed", name);
return NULL;
}
wlan_sm_history_init(sm);
sm->cur_state = init_state;
sm->num_states = num_states;
sm->state_info = state_info;
sm->ctx = ctx;
sm->last_event = WLAN_SM_ENGINE_EVENT_NONE;
qdf_atomic_set(&sm->in_state_transition, 0);
sm->event_names = event_names;
sm->num_event_names = num_event_names;
qdf_str_lcopy(sm->name, name, WLAN_SM_ENGINE_MAX_NAME);
sm_engine_debug("%s: sm creation successful", name);
return sm;
}
qdf_export_symbol(wlan_sm_create);
void wlan_sm_delete(struct wlan_sm *sm)
{
wlan_sm_history_delete(sm);
qdf_mem_free(sm);
}
qdf_export_symbol(wlan_sm_delete);
uint8_t wlan_sm_get_lastevent(struct wlan_sm *sm)
{
return sm->last_event;
}
uint8_t wlan_sm_get_current_state(struct wlan_sm *sm)
{
return sm->cur_state;
}
qdf_export_symbol(wlan_sm_get_current_state);
const char *wlan_sm_get_state_name(struct wlan_sm *sm, uint8_t state)
{
return sm->state_info[state].name;
}
const char *wlan_sm_get_current_state_name(struct wlan_sm *sm)
{
return sm->state_info[sm->cur_state].name;
}
qdf_export_symbol(wlan_sm_get_current_state_name);