project:cfg:BoardConfig_IPC: Added fastboot BoardConfig file and firmware post-scripts, distinguishing between the BoardConfigs for Luckfox Pico Pro and Luckfox Pico Max. project:app: Added fastboot_client and rk_smart_door for quick boot applications; updated rkipc app to adapt to the latest media library. media:samples: Added more usage examples. media:rockit: Fixed bugs; removed support for retrieving data frames from VPSS. media:isp: Updated rkaiq library and related tools to support connection to RKISP_Tuner. sysdrv:Makefile: Added support for compiling drv_ko on Luckfox Pico Ultra W using Ubuntu; added support for custom root filesystem. sysdrv:tools:board: Updated Buildroot optional mirror sources, updated some software versions, and stored device tree files and configuration files that undergo multiple modifications for U-Boot and kernel separately. sysdrv:source:mcu: Used RISC-V MCU SDK with RT-Thread system, mainly for initializing camera AE during quick boot. sysdrv:source:uboot: Added support for fastboot; added high baud rate DDR bin for serial firmware upgrades. sysdrv:source:kernel: Upgraded to version 5.10.160; increased NPU frequency for RV1106G3; added support for fastboot. Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
962 lines
34 KiB
C
962 lines
34 KiB
C
/*
|
|
* Copyright (c) 2021 iComm-semi Ltd.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* @file fmac_msg_rx.c
|
|
* @brief RX function definitions
|
|
*/
|
|
|
|
|
|
/*******************************************************************************
|
|
* Include Files
|
|
******************************************************************************/
|
|
#include <linux/version.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "fmac.h"
|
|
#include "hci/drv_hci_ops.h"
|
|
#include "fmac_defs.h"
|
|
#include "fmac_strs.h"
|
|
#include "fmac_tx.h"
|
|
#include "fmac_rx.h"
|
|
#include "fmac_msg_tx.h"
|
|
|
|
#include "ssv_cfg.h"
|
|
#include "ssv_debug.h"
|
|
#include <linux/semaphore.h>
|
|
|
|
|
|
/*******************************************************************************
|
|
* Local Defines
|
|
******************************************************************************/
|
|
static inline int ssv_rx_chan_switch_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_chan_pre_switch_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_remain_on_channel_exp_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_ps_change_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_traffic_req_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_csa_counter_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_csa_finish_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_csa_traffic_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_channel_survey_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_rssi_status_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_scanu_start_cfm(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_scanu_result_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_me_tkip_mic_failure_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_me_tx_credits_update_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_sm_connect_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_sm_disconnect_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_sm_external_auth_required_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
static inline int ssv_rx_dbg_error_ind(struct ssv_softc *sc, struct ssv_cmd *cmd, struct ipc_e2a_msg *msg);
|
|
|
|
#ifdef CONFIG_SSV_CHANNEL_FOLLOW
|
|
extern void ssv_channel_switch_check(struct ssv_softc *sc, u16 freq, enum nl80211_channel_type chann_type);
|
|
#endif /*CONFIG_SSV_CHANNEL_FOLLOW */
|
|
|
|
/*******************************************************************************
|
|
* Local Enumerations
|
|
******************************************************************************/
|
|
#if defined(CONFIG_WPA_SUPPLICANT_CTL)
|
|
enum ssv_external_auth_action {
|
|
SSV_EXTERNAL_AUTH_START,
|
|
SSV_EXTERNAL_AUTH_ABORT,
|
|
};
|
|
#endif
|
|
|
|
|
|
/*******************************************************************************
|
|
* Local Structures
|
|
******************************************************************************/
|
|
#if defined(CONFIG_WPA_SUPPLICANT_CTL)
|
|
struct ssv_external_auth_params {
|
|
enum ssv_external_auth_action action;
|
|
u8 bssid[ETH_ALEN] __aligned(2);
|
|
struct cfg80211_ssid ssid;
|
|
unsigned int key_mgmt_suite;
|
|
u16 status;
|
|
};
|
|
#endif
|
|
|
|
|
|
/*******************************************************************************
|
|
* Global Variables
|
|
******************************************************************************/
|
|
extern struct ssv6xxx_cfg ssv_cfg;
|
|
|
|
/*******************************************************************************
|
|
* Local Variables
|
|
******************************************************************************/
|
|
static msg_cb_fct mm_hdlrs[MSG_I(MM_MAX)] = {
|
|
[MSG_I(MM_CHANNEL_SWITCH_IND)] = ssv_rx_chan_switch_ind,
|
|
[MSG_I(MM_CHANNEL_PRE_SWITCH_IND)] = ssv_rx_chan_pre_switch_ind,
|
|
[MSG_I(MM_REMAIN_ON_CHANNEL_EXP_IND)] = ssv_rx_remain_on_channel_exp_ind,
|
|
[MSG_I(MM_PS_CHANGE_IND)] = ssv_rx_ps_change_ind,
|
|
[MSG_I(MM_TRAFFIC_REQ_IND)] = ssv_rx_traffic_req_ind,
|
|
[MSG_I(MM_CSA_COUNTER_IND)] = ssv_rx_csa_counter_ind,
|
|
[MSG_I(MM_CSA_FINISH_IND)] = ssv_rx_csa_finish_ind,
|
|
[MSG_I(MM_CSA_TRAFFIC_IND)] = ssv_rx_csa_traffic_ind,
|
|
[MSG_I(MM_CHANNEL_SURVEY_IND)] = ssv_rx_channel_survey_ind,
|
|
[MSG_I(MM_RSSI_STATUS_IND)] = ssv_rx_rssi_status_ind,
|
|
};
|
|
|
|
static msg_cb_fct scan_hdlrs[MSG_I(SCANU_MAX)] = {
|
|
[MSG_I(SCANU_START_CFM)] = ssv_rx_scanu_start_cfm,
|
|
[MSG_I(SCANU_RESULT_IND)] = ssv_rx_scanu_result_ind,
|
|
};
|
|
|
|
static msg_cb_fct me_hdlrs[MSG_I(ME_MAX)] = {
|
|
[MSG_I(ME_TKIP_MIC_FAILURE_IND)] = ssv_rx_me_tkip_mic_failure_ind,
|
|
[MSG_I(ME_TX_CREDITS_UPDATE_IND)] = ssv_rx_me_tx_credits_update_ind,
|
|
};
|
|
|
|
static msg_cb_fct sm_hdlrs[MSG_I(SM_MAX)] = {
|
|
[MSG_I(SM_CONNECT_IND)] = ssv_rx_sm_connect_ind,
|
|
[MSG_I(SM_DISCONNECT_IND)] = ssv_rx_sm_disconnect_ind,
|
|
[MSG_I(SM_EXTERNAL_AUTH_REQUIRED_IND)] = ssv_rx_sm_external_auth_required_ind,
|
|
};
|
|
|
|
static msg_cb_fct apm_hdlrs[MSG_I(APM_MAX)] = {
|
|
};
|
|
|
|
static msg_cb_fct dbg_hdlrs[MSG_I(DBG_MAX)] = {
|
|
[MSG_I(DBG_ERROR_IND)] = ssv_rx_dbg_error_ind,
|
|
};
|
|
|
|
static msg_cb_fct *msg_hdlrs[] = {
|
|
[TASK_MM] = mm_hdlrs,
|
|
[TASK_DBG] = dbg_hdlrs,
|
|
[TASK_SCANU] = scan_hdlrs,
|
|
[TASK_ME] = me_hdlrs,
|
|
[TASK_SM] = sm_hdlrs,
|
|
[TASK_APM] = apm_hdlrs,
|
|
};
|
|
|
|
|
|
/*******************************************************************************
|
|
* Local Functions
|
|
******************************************************************************/
|
|
static int ssv_freq_to_idx(struct ssv_softc *sc, int freq)
|
|
{
|
|
struct ieee80211_supported_band *sband;
|
|
int band, ch, idx = 0;
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0))
|
|
for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) {
|
|
#else
|
|
for (band = IEEE80211_BAND_2GHZ; band < IEEE80211_NUM_BANDS; band++) {
|
|
#endif
|
|
sband = sc->wiphy->bands[band];
|
|
if (!sband) {
|
|
continue;
|
|
}
|
|
|
|
for (ch = 0; ch < sband->n_channels; ch++, idx++) {
|
|
if (sband->channels[ch].center_freq == freq) {
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
BUG_ON(1);
|
|
|
|
exit:
|
|
// Channel has been found, return the index
|
|
return idx;
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Messages from MM task
|
|
**************************************************************************/
|
|
static inline int ssv_rx_chan_pre_switch_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
//do nothing
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_chan_switch_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
int chan_idx = ((struct mm_channel_switch_ind *)msg->param)->chan_index;
|
|
struct ssv_vif *ssv_vif;
|
|
bool roc = ((struct mm_channel_switch_ind *)msg->param)->roc;
|
|
|
|
if (roc) {
|
|
/* Retrieve the allocated RoC element */
|
|
struct ssv_roc_elem *roc_elem;
|
|
|
|
roc_elem = sc->roc_elem;
|
|
|
|
if (!roc_elem) {
|
|
goto out;
|
|
}
|
|
|
|
/* Get VIF on which RoC has been started */
|
|
ssv_vif = netdev_priv(roc_elem->wdev->netdev);
|
|
|
|
/* Keep in mind that we have switched on the channel */
|
|
roc_elem->on_chan = true;
|
|
|
|
/* If mgmt_roc is true, remain on channel has been started by ourself */
|
|
if (!roc_elem->mgmt_roc) {
|
|
/* Inform the host that we have switch on the indicated off-channel */
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0))
|
|
cfg80211_ready_on_channel(roc_elem->wdev, (u64)(sc->roc_cookie_cnt),
|
|
roc_elem->chan, roc_elem->duration, GFP_KERNEL);
|
|
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0))
|
|
cfg80211_ready_on_channel(roc_elem->wdev, (u64)(sc->roc_cookie_cnt),
|
|
roc_elem->chan, NL80211_CHAN_NO_HT,
|
|
roc_elem->duration, GFP_KERNEL);
|
|
#else
|
|
cfg80211_ready_on_channel(roc_elem->wdev->netdev, (u64)(sc->roc_cookie_cnt),
|
|
roc_elem->chan, NL80211_CHAN_NO_HT,
|
|
roc_elem->duration, GFP_KERNEL);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
out:
|
|
sc->cur_chanctx = chan_idx;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_remain_on_channel_exp_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
/* Retrieve the allocated RoC element */
|
|
struct ssv_roc_elem *roc_elem;
|
|
struct wireless_dev *wdev;
|
|
struct ieee80211_channel *chan;
|
|
bool mgmt_roc, on_chan;
|
|
u32 roc_cookie_cnt;
|
|
|
|
roc_elem = sc->roc_elem;
|
|
|
|
if (!roc_elem) {
|
|
SSV_LOG_DBG("%s() roc_elem is null\n", __FUNCTION__);
|
|
return 0;
|
|
}
|
|
|
|
wdev = roc_elem->wdev;
|
|
chan = roc_elem->chan;
|
|
mgmt_roc = roc_elem->mgmt_roc;
|
|
on_chan = roc_elem->on_chan;
|
|
roc_cookie_cnt = sc->roc_cookie_cnt;
|
|
|
|
/* Free the allocated RoC element */
|
|
kfree(sc->roc_elem);
|
|
sc->roc_elem = NULL;
|
|
|
|
/* Increase the cookie counter cannot be zero */
|
|
sc->roc_cookie_cnt++;
|
|
|
|
if (sc->roc_cookie_cnt == 0) {
|
|
sc->roc_cookie_cnt = 1;
|
|
}
|
|
|
|
/* If mgmt_roc is true, remain on channel has been started by ourself */
|
|
/* If RoC has been cancelled before we switched on channel, do not call cfg80211 */
|
|
if (!mgmt_roc && on_chan) {
|
|
/* Inform the host that off-channel period has expired */
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0))
|
|
cfg80211_remain_on_channel_expired(wdev, (u64)roc_cookie_cnt,
|
|
chan, GFP_KERNEL);
|
|
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0))
|
|
cfg80211_remain_on_channel_expired(wdev, (u64)roc_cookie_cnt,
|
|
chan, NL80211_CHAN_NO_HT, GFP_KERNEL);
|
|
#else
|
|
cfg80211_remain_on_channel_expired(wdev->netdev, (u64)roc_cookie_cnt,
|
|
chan, NL80211_CHAN_NO_HT, GFP_KERNEL);
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_p2p_vif_ps_change_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
int vif_idx = ((struct mm_p2p_vif_ps_change_ind *)msg->param)->vif_index;
|
|
struct ssv_vif *vif_entry;
|
|
|
|
vif_entry = sc->vif_table[vif_idx];
|
|
|
|
if (vif_entry) {
|
|
goto found_vif;
|
|
}
|
|
|
|
goto exit;
|
|
|
|
found_vif:
|
|
|
|
exit:
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_channel_survey_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct mm_channel_survey_ind *ind = (struct mm_channel_survey_ind *)msg->param;
|
|
// Get the channel index
|
|
int idx = ssv_freq_to_idx(sc, ind->freq);
|
|
// Get the survey
|
|
struct ssv_survey_info *ssv_survey = &sc->survey[idx];
|
|
|
|
|
|
|
|
// Store the received parameters
|
|
ssv_survey->chan_time_ms = ind->chan_time_ms;
|
|
ssv_survey->chan_time_busy_ms = ind->chan_time_busy_ms;
|
|
ssv_survey->noise_dbm = ind->noise_dbm;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,0,0)
|
|
ssv_survey->filled = (SURVEY_INFO_TIME |
|
|
SURVEY_INFO_TIME_BUSY);
|
|
#else
|
|
ssv_survey->filled = (SURVEY_INFO_CHANNEL_TIME |
|
|
SURVEY_INFO_CHANNEL_TIME_BUSY);
|
|
#endif
|
|
|
|
if (ind->noise_dbm != 0) {
|
|
ssv_survey->filled |= SURVEY_INFO_NOISE_DBM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_rssi_status_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
#define RX_RSSI_NOTIFY_CFG80211_ENABLE (0)
|
|
struct mm_rssi_status_ind *ind = (struct mm_rssi_status_ind *)msg->param;
|
|
int vif_idx = ind->vif_index;
|
|
#if (RX_RSSI_NOTIFY_CFG80211_ENABLE == 1)
|
|
bool rssi_status = ind->rssi_status;
|
|
#endif
|
|
int8_t rssi = ind->rssi;
|
|
struct ssv_vif *vif_entry;
|
|
int i = 0 ;
|
|
|
|
vif_entry = sc->vif_table[vif_idx];
|
|
if(sc->rssicnt == 0) {
|
|
sc->rssicnt = RSSI_MAX;
|
|
//memset(sc->rssival, rssi, RSSI_MAX);
|
|
for (i = 0; i < RSSI_MAX; i++)
|
|
{
|
|
sc->rssival[i]=rssi;
|
|
}
|
|
sc->rssiaccu = rssi * RSSI_MAX;
|
|
sc->rssiidx = 0;
|
|
} else {
|
|
sc->rssiaccu = sc->rssiaccu - sc->rssival[sc->rssiidx] + rssi;
|
|
sc->rssival[sc->rssiidx] = rssi;
|
|
sc->rssiidx = ((sc->rssiidx + 1) % RSSI_MAX);
|
|
}
|
|
|
|
#if (RX_RSSI_NOTIFY_CFG80211_ENABLE == 1)
|
|
if (vif_entry) {
|
|
cfg80211_cqm_rssi_notify(vif_entry->ndev,
|
|
rssi_status ? NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW :
|
|
NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
|
|
0,
|
|
#endif
|
|
GFP_KERNEL);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_csa_counter_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct mm_csa_counter_ind *ind = (struct mm_csa_counter_ind *)msg->param;
|
|
struct ssv_vif *vif;
|
|
bool found = false;
|
|
|
|
|
|
|
|
// Look for VIF entry
|
|
list_for_each_entry(vif, &sc->vifs, list) {
|
|
if (vif->drv_vif_index == ind->vif_index) {
|
|
found=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
if (vif->ap.csa)
|
|
vif->ap.csa->count = ind->csa_count;
|
|
else
|
|
netdev_err(vif->ndev, "CSA counter update but no active CSA");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_csa_finish_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct mm_csa_finish_ind *ind = (struct mm_csa_finish_ind *)msg->param;
|
|
struct ssv_vif *vif;
|
|
bool found = false;
|
|
|
|
|
|
|
|
// Look for VIF entry
|
|
list_for_each_entry(vif, &sc->vifs, list) {
|
|
if (vif->drv_vif_index == ind->vif_index) {
|
|
found=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
if (SSV_VIF_TYPE(vif) == NL80211_IFTYPE_AP ||
|
|
SSV_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_GO) {
|
|
if (vif->ap.csa) {
|
|
vif->ap.csa->status = ind->status;
|
|
vif->ap.csa->ch_idx = ind->chan_idx;
|
|
schedule_work(&vif->ap.csa->work);
|
|
} else
|
|
netdev_err(vif->ndev, "CSA finish indication but no active CSA");
|
|
} else {
|
|
if (ind->status == 0) {
|
|
ssv_chanctx_unlink(vif);
|
|
ssv_chanctx_link(vif, ind->chan_idx, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_csa_traffic_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct mm_csa_traffic_ind *ind = (struct mm_csa_traffic_ind *)msg->param;
|
|
struct ssv_vif *vif;
|
|
bool found = false;
|
|
|
|
|
|
|
|
// Look for VIF entry
|
|
list_for_each_entry(vif, &sc->vifs, list) {
|
|
if (vif->drv_vif_index == ind->vif_index) {
|
|
found=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_ps_change_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct mm_ps_change_ind *ind = (struct mm_ps_change_ind *)msg->param;
|
|
struct ssv_sta *sta = &sc->sta_table[ind->sta_idx];
|
|
|
|
#if 0
|
|
SSV_LOG_DBG(KERN_ERR"Sta %d, change PS mode to %s", sta->sta_idx,
|
|
ind->ps_state ? "ON" : "OFF");
|
|
#endif
|
|
|
|
if (sta->valid) {
|
|
ssv_ps_bh_enable(sc, sta, ind->ps_state);
|
|
} else if (sc->adding_sta) {
|
|
sta->ps.active = ind->ps_state ? true : false;
|
|
} else {
|
|
SSV_LOG_DBG(KERN_ERR"Ignore PS mode change on invalid sta\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline int ssv_rx_traffic_req_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct mm_traffic_req_ind *ind = (struct mm_traffic_req_ind *)msg->param;
|
|
struct ssv_sta *sta = &sc->sta_table[ind->sta_idx];
|
|
|
|
#if 0
|
|
SSV_LOG_DBG(KERN_ERR"Sta %d, asked for %d pkt", sta->sta_idx, ind->pkt_cnt);
|
|
#endif
|
|
|
|
ssv_ps_bh_traffic_req(sc, sta, ind->pkt_cnt,
|
|
ind->uapsd ? UAPSD_ID : LEGACY_PS_ID);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Messages from SCANU task
|
|
**************************************************************************/
|
|
static inline int ssv_rx_scanu_start_cfm(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0))
|
|
struct cfg80211_scan_info info = {
|
|
.aborted = false,
|
|
};
|
|
#endif
|
|
|
|
|
|
if (sc->scan_request)
|
|
{
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0))
|
|
cfg80211_scan_done(sc->scan_request, false);
|
|
#else
|
|
cfg80211_scan_done(sc->scan_request, &info);
|
|
#endif
|
|
}
|
|
|
|
sc->scan_request = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_scanu_result_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct cfg80211_bss *bss = NULL;
|
|
struct ieee80211_channel *chan;
|
|
struct scanu_result_ind *ind = (struct scanu_result_ind *)msg->param;
|
|
|
|
chan = ieee80211_get_channel(sc->wiphy, ind->center_freq);
|
|
|
|
if (chan != NULL)
|
|
bss = cfg80211_inform_bss_frame(sc->wiphy, chan,
|
|
(struct ieee80211_mgmt *)ind->payload,
|
|
ind->length, ind->rssi * 100, GFP_ATOMIC);
|
|
|
|
if (bss != NULL)
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0))
|
|
cfg80211_put_bss(sc->wiphy, bss);
|
|
#else
|
|
cfg80211_put_bss(bss);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Messages from ME task
|
|
**************************************************************************/
|
|
static inline int ssv_rx_me_tkip_mic_failure_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct me_tkip_mic_failure_ind *ind = (struct me_tkip_mic_failure_ind *)msg->param;
|
|
struct ssv_vif *ssv_vif = sc->vif_table[ind->vif_idx];
|
|
struct net_device *dev = ssv_vif->ndev;
|
|
|
|
|
|
|
|
cfg80211_michael_mic_failure(dev, (u8 *)&ind->addr, (ind->ga?NL80211_KEYTYPE_GROUP:
|
|
NL80211_KEYTYPE_PAIRWISE), ind->keyid,
|
|
(u8 *)&ind->tsc, GFP_ATOMIC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_me_tx_credits_update_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
// struct me_tx_credits_update_ind *ind = (struct me_tx_credits_update_ind *)msg->param;
|
|
return 0;
|
|
}
|
|
|
|
static const int chnl2bw[] = {
|
|
[PHY_CHNL_BW_20] = NL80211_CHAN_WIDTH_20,
|
|
[PHY_CHNL_BW_40] = NL80211_CHAN_WIDTH_40,
|
|
[PHY_CHNL_BW_80] = NL80211_CHAN_WIDTH_80,
|
|
[PHY_CHNL_BW_160] = NL80211_CHAN_WIDTH_160,
|
|
[PHY_CHNL_BW_80P80] = NL80211_CHAN_WIDTH_80P80,
|
|
};
|
|
/***************************************************************************
|
|
* Messages from SM task
|
|
**************************************************************************/
|
|
static inline int ssv_rx_sm_connect_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
struct sm_connect_ind *ind = (struct sm_connect_ind *)msg->param;
|
|
struct ssv_vif *ssv_vif = sc->vif_table[ind->vif_idx];
|
|
struct net_device *dev = ssv_vif->ndev;
|
|
const u8 *req_ie, *rsp_ie;
|
|
|
|
#ifdef CONFIG_SSV_CHANNEL_FOLLOW
|
|
u16 freq = -1;
|
|
enum nl80211_channel_type ch_type = NL80211_CHAN_NO_HT;
|
|
struct cfg80211_chan_def chandef;
|
|
|
|
memset((void *)&chandef, 0, sizeof(struct cfg80211_chan_def));
|
|
if(ind->chan.prim20_freq == 0) { // if connect fail, find freq from cfg80211_connect_params.
|
|
if(NL80211_IFTYPE_STATION == SSV_VIF_TYPE(ssv_vif) && ssv_vif->ch_hint) {
|
|
freq = ssv_vif->ch_hint;
|
|
ch_type = ssv_vif->chtype_hint;
|
|
}
|
|
} else {
|
|
freq = ind->chan.prim20_freq;
|
|
chandef.width = chnl2bw[ind->chan.type];
|
|
chandef.chan = ieee80211_get_channel(sc->wiphy, freq);
|
|
chandef.center_freq1 = ind->chan.center1_freq;
|
|
chandef.center_freq2 = ind->chan.center2_freq;
|
|
ch_type = cfg80211_get_chandef_type(&chandef);
|
|
|
|
}
|
|
|
|
SSV_LOG_DBG("[%s][%d] freq = %u, ch_type = %d\n", __FUNCTION__, __LINE__, freq, ch_type);
|
|
ssv_channel_switch_check(sc, freq, ch_type);
|
|
#endif /*CONFIG_SSV_CHANNEL_FOLLOW */
|
|
|
|
SSV_LOG_DBG("[%s][%d] status = %u\n", __FUNCTION__, __LINE__, ind->status_code);
|
|
SSV_LOG_DBG("[%s][%d] ind->ap_idx = %u\n", __FUNCTION__, __LINE__, ind->ap_idx);
|
|
|
|
|
|
if(ssv_cfg.sta_max_reconnect_times)
|
|
{
|
|
//Retry_Connection doesn't support WPA3
|
|
if(sc->sta_reconnect_info.auth_type != NL80211_AUTHTYPE_SAE)
|
|
{
|
|
/*If connection failed, retry again */
|
|
if(ind->status_code &&
|
|
( sc->sta_reconnect_info.retry_times < ssv_cfg.sta_max_reconnect_times))
|
|
{
|
|
sc->sta_reconnect_info.connect_retry = true;
|
|
sc->sta_reconnect_info.retry_times++;
|
|
SSV_LOG_DBG("Connection retry_times %d\n", sc->sta_reconnect_info.retry_times);
|
|
}else{
|
|
sc->sta_reconnect_info.connect_retry = false;
|
|
sc->sta_reconnect_info.retry_times = 0;
|
|
}
|
|
up(&sc->reconnect_sem);
|
|
|
|
if(sc->sta_reconnect_info.connect_retry)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Retrieve IE addresses and lengths */
|
|
req_ie = (const u8 *)ind->assoc_ie_buf;
|
|
rsp_ie = req_ie + ind->assoc_req_ie_len;
|
|
|
|
|
|
// Fill-in the AP information
|
|
if (ind->status_code == 0)
|
|
{
|
|
struct ssv_sta *sta = &sc->sta_table[ind->ap_idx];
|
|
sta->valid = true;
|
|
sta->sta_idx = ind->ap_idx;
|
|
sta->ch_idx = ind->ch_idx;
|
|
sta->vif_idx = ind->vif_idx;
|
|
sta->vlan_idx = sta->vif_idx;
|
|
sta->qos = ind->qos;
|
|
sta->acm = ind->acm;
|
|
sta->ps.active = false;
|
|
sta->aid = ind->aid;
|
|
sta->band = ind->chan.band;
|
|
sta->width = chnl2bw[ind->chan.type];
|
|
sta->center_freq = ind->chan.prim20_freq;
|
|
sta->center_freq1 = ind->chan.center1_freq;
|
|
sta->center_freq2 = ind->chan.center2_freq;
|
|
sta->probe_timestamp = 0;
|
|
ssv_vif->sta.ap = sta;
|
|
// @TODO: Get chan def in this case (add params in cfm ??)
|
|
{
|
|
struct cfg80211_chan_def chan_def;
|
|
memset((void *)&chan_def, 0, sizeof(struct cfg80211_chan_def));
|
|
chan_def.chan = ieee80211_get_channel(sc->wiphy, sta->center_freq);
|
|
chan_def.width = sta->width;
|
|
chan_def.center_freq1 = sta->center_freq1;
|
|
chan_def.center_freq2 = sta->center_freq2;
|
|
ssv_chanctx_link(ssv_vif, ind->ch_idx, &chan_def);
|
|
}
|
|
memcpy(sta->mac_addr, ind->bssid.array, ETH_ALEN);
|
|
memcpy(sta->ac_param, ind->ac_param, sizeof(sta->ac_param));
|
|
ssv_rxreord_create(sc, sta->sta_idx);
|
|
sta->port_control=(true==ssv_vif->need_port_control)?true:false;
|
|
// ssv_dbgfs_register_rc_stat(sc, sta);
|
|
|
|
ssv_drv_hci_tx_active_by_sta(sc->hci_priv, sc->hci_ops, sta->sta_idx); //active hci mng. queue
|
|
ssv_drv_hci_tx_resume_by_sta(sc->hci_priv, sc->hci_ops, sta->sta_idx); //resume hci mng. queue
|
|
// WMM
|
|
if (1 == ssv_cfg.wmm_follow_vo) {
|
|
u32 wmm_param = 0;
|
|
ssv_drv_hci_read_word(sc->hci_priv, sc->hci_ops, 0x0800020c, &wmm_param);
|
|
SSV_LOG_DBG("##### VO wmm param 0x%08x\n", wmm_param);
|
|
ssv_drv_hci_write_word(sc->hci_priv, sc->hci_ops, 0x08000200, wmm_param);
|
|
ssv_drv_hci_write_word(sc->hci_priv, sc->hci_ops, 0x08000204, wmm_param);
|
|
ssv_drv_hci_write_word(sc->hci_priv, sc->hci_ops, 0x08000208, wmm_param);
|
|
}
|
|
}
|
|
|
|
SSV_LOG_DBG("[%s][%d] ind->status_code = %u\n", __FUNCTION__, __LINE__, ind->status_code);
|
|
if (!ind->roamed)
|
|
{
|
|
SSV_LOG_DBG("[%s][%d] ind->status_code = %u\n", __FUNCTION__, __LINE__, ind->status_code);
|
|
cfg80211_connect_result(dev, (const u8 *)ind->bssid.array, req_ie,
|
|
ind->assoc_req_ie_len, rsp_ie,
|
|
ind->assoc_rsp_ie_len, ind->status_code,
|
|
GFP_ATOMIC);
|
|
}
|
|
|
|
if (ind->status_code == 0)
|
|
{
|
|
netif_tx_start_all_queues(dev);
|
|
netif_carrier_on(dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_sm_disconnect_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
#define SSV_RC_BEACON_MISS 5000
|
|
struct sm_disconnect_ind *ind = (struct sm_disconnect_ind *)msg->param;
|
|
struct ssv_vif *ssv_vif = sc->vif_table[ind->vif_idx];
|
|
struct net_device *dev = ssv_vif->ndev;
|
|
int j = 0;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
|
|
bool bConned = true;
|
|
#endif
|
|
struct cfg80211_bss *bss;
|
|
|
|
/* if vif is not up, ssv_close has already been called */
|
|
if (ssv_vif->up) {
|
|
netif_tx_stop_all_queues(dev);
|
|
netif_carrier_off(dev);
|
|
}
|
|
|
|
ssv_drv_hci_tx_pause_by_sta(sc->hci_priv, sc->hci_ops, ssv_vif->sta.ap->sta_idx); //pause hci tx queue by sta
|
|
ssv_drv_hci_tx_inactive_by_sta(sc->hci_priv, sc->hci_ops, ssv_vif->sta.ap->sta_idx); //inactive hci tx queue by sta
|
|
|
|
memset(&sc->rx_bysta[ssv_vif->sta.ap->sta_idx], 0 , sizeof(struct ssv_rx));
|
|
memset(&sc->tx_bysta[ssv_vif->sta.ap->sta_idx], 0 , sizeof(struct ssv_tx));
|
|
|
|
ssv_rxreord_delete(sc, ssv_vif->sta.ap->sta_idx);
|
|
// ssv_dbgfs_unregister_rc_stat(sc, ssv_vif->sta.ap);
|
|
SSV_LOG_DBG("[%s][%d] ssv_vif->sta.ap->sta_idx = %u\n", __FUNCTION__, __LINE__, ssv_vif->sta.ap->sta_idx);
|
|
SSV_LOG_DBG("disconnect reason code = %d\n", ind->reason_code);
|
|
if(ind->reason_code == SSV_RC_BEACON_MISS)
|
|
SSV_LOG_DBG("beacon miss!\n");
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0))
|
|
bss = cfg80211_get_bss(sc->wiphy, NULL, ssv_vif->sta.ap->mac_addr, NULL, 0, IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY);
|
|
#else
|
|
bss = cfg80211_get_bss(sc->wiphy, NULL, ssv_vif->sta.ap->mac_addr, NULL, 0, 0, 0);
|
|
#endif
|
|
if (bss) {
|
|
/* AP probably changes its channel, so remove the bss struct for that AP. */
|
|
cfg80211_unlink_bss(sc->wiphy, bss);
|
|
cfg80211_put_bss(sc->wiphy, bss);
|
|
}
|
|
|
|
ssv_vif->sta.ap->valid = false;
|
|
//Reset station last sequence
|
|
for(j = 0; j < TID_MAX ; j++){
|
|
ssv_vif->sta.ap->rx_last_seqcntl[j] = 0xFFFF;
|
|
}
|
|
ssv_vif->sta.ap->port_control=true;
|
|
// memset(&ssv_vif->sta.ap->mac_addr[0], 0, ETH_ALEN);
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
|
|
bConned=(ssv_vif->sta.ap!=NULL)?true:false;
|
|
#endif
|
|
ssv_vif->sta.ap = NULL;
|
|
ssv_external_auth_disable(ssv_vif);
|
|
ssv_chanctx_unlink(ssv_vif);
|
|
|
|
/* if vif is not up, ssv_close has already been called */
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
|
|
if (ssv_vif->up && (bConned == true)) {
|
|
#else
|
|
if (ssv_vif->up && (ssv_vif->wdev.sme_state == CFG80211_SME_CONNECTED)) {
|
|
#endif
|
|
//if (!ind->ft_over_ds) {
|
|
if (!ind->reassoc)
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
|
|
cfg80211_disconnected(dev, ind->reason_code, NULL, 0, true, GFP_ATOMIC);
|
|
#else
|
|
cfg80211_disconnected(dev, ind->reason_code, NULL, 0, GFP_ATOMIC);
|
|
#endif
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ssv_rx_sm_disconnect_ind_for_fw_reset(struct ssv_softc *sc, int idx)
|
|
{
|
|
struct sm_disconnect_ind *ind;
|
|
struct ipc_e2a_msg *msg;
|
|
struct ssv_vif *vif = sc->vif_table[sc->if_mode[idx][1]];
|
|
u16 ind_len;
|
|
|
|
SSV_LOG_DBG("[%s][%d] index = %d\n", __FUNCTION__, __LINE__, idx);
|
|
ind = kmalloc(sizeof(struct sm_disconnect_ind), GFP_KERNEL);
|
|
memset(ind, 0, sizeof(struct sm_disconnect_ind));
|
|
ind->reason_code = 0x3;
|
|
ind->vif_idx = vif->drv_vif_index;
|
|
ind->reassoc = 0x0;
|
|
ind_len = sizeof(struct sm_disconnect_ind);
|
|
|
|
msg = kmalloc((sizeof (struct ipc_e2a_msg) + ind_len), GFP_KERNEL); //造成memory leak?
|
|
if (!msg) {
|
|
SSV_LOG_DBG("allocate fw reset msg fail.\n");
|
|
kfree(ind);
|
|
return;
|
|
}
|
|
|
|
memcpy(msg->param, ind, ind_len);
|
|
msg->param_len = ind_len;
|
|
|
|
SSV_LOG_DBG("FW RESET disconnect.\n");
|
|
ssv_rx_sm_disconnect_ind(sc, NULL, msg);
|
|
kfree(msg);
|
|
kfree(ind);
|
|
}
|
|
|
|
#if defined (CONFIG_WPA_SUPPLICANT_CTL)
|
|
extern int ssv_wpas_ctl_extauth_req(void *data, int size);
|
|
#endif
|
|
static inline int ssv_rx_sm_external_auth_required_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) || defined(CONFIG_SUPPORT_WPA3))
|
|
struct sm_external_auth_required_ind *ind = (struct sm_external_auth_required_ind *)msg->param;
|
|
struct ssv_vif *ssv_vif = sc->vif_table[ind->vif_idx];
|
|
struct net_device *dev = ssv_vif->ndev;
|
|
struct cfg80211_external_auth_params params;
|
|
|
|
if (IEEE80211_MAX_SSID_LEN < ind->ssid.length) {
|
|
SSV_LOG_ERR("Invild ssid len %d\n", ind->ssid.length);
|
|
WARN_ON(1);
|
|
return 0;
|
|
}
|
|
|
|
memset(¶ms, 0, sizeof(struct cfg80211_external_auth_params));
|
|
params.action = NL80211_EXTERNAL_AUTH_START;
|
|
params.key_mgmt_suite = ind->akm;
|
|
memcpy(params.bssid, ind->bssid.array, ETH_ALEN);
|
|
params.ssid.ssid_len = ind->ssid.length;
|
|
memcpy(params.ssid.ssid, ind->ssid.array, ind->ssid.length);
|
|
|
|
cfg80211_external_auth_request(dev, ¶ms, GFP_ATOMIC);
|
|
ssv_external_auth_enable(ssv_vif);
|
|
#elif defined(CONFIG_WPA_SUPPLICANT_CTL)
|
|
struct sm_external_auth_required_ind *ind = (struct sm_external_auth_required_ind *)msg->param;
|
|
struct ssv_vif *ssv_vif = sc->vif_table[ind->vif_idx];
|
|
struct ssv_external_auth_params params;
|
|
|
|
SSV_LOG_DBG("External auth required.\n");
|
|
if (IEEE80211_MAX_SSID_LEN < ind->ssid.length) {
|
|
SSV_LOG_ERR("Invild ssid len %d\n", ind->ssid.length);
|
|
WARN_ON(1);
|
|
return 0;
|
|
}
|
|
|
|
memset(¶ms, 0, sizeof(struct ssv_external_auth_params));
|
|
params.action = SSV_EXTERNAL_AUTH_START;
|
|
params.key_mgmt_suite = ind->akm;
|
|
memcpy(params.bssid, ind->bssid.array, ETH_ALEN);
|
|
params.ssid.ssid_len = ind->ssid.length;
|
|
memcpy(params.ssid.ssid, ind->ssid.array, ind->ssid.length);
|
|
ssv_wpas_ctl_extauth_req(¶ms, sizeof(struct ssv_external_auth_params));
|
|
ssv_external_auth_enable(ssv_vif);
|
|
#else
|
|
SSV_LOG_DBG("External auth required, checking kernel version or config\n");
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static inline int ssv_rx_dbg_error_ind(struct ssv_softc *sc,
|
|
struct ssv_cmd *cmd,
|
|
struct ipc_e2a_msg *msg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline int _ssv_rx_check_msg(struct ipc_e2a_msg *msg)
|
|
{
|
|
if (strcmp("unknown", SSV_ID2STR(msg->id)) == 0)
|
|
{
|
|
SSV_LOG_ERR("no msg(%4d) handler, pass the msg!!\n", msg->id);
|
|
//BUG_ON(1);
|
|
return -1;
|
|
}
|
|
|
|
if(ssv_cfg.dump_fmac_msg_rx)
|
|
SSV_LOG_DBG_DUMP("recv msg:", msg->param, msg->param_len);
|
|
|
|
return 0;
|
|
#if 0 //for debug
|
|
switch(msg->id)
|
|
{
|
|
case MM_RSSI_STATUS_IND:
|
|
{
|
|
// SSV_LOG_DBG("[%s] MM_RSSI_STATUS_IND\n", __FUNCTION__);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
SSV_LOG_DBG("[%s] recv: msg:%4d-%-24s\n", __FUNCTION__, msg->id, SSV_ID2STR(msg->id));
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Global Functions
|
|
******************************************************************************/
|
|
/**
|
|
*
|
|
*/
|
|
int ssv_rx_handle_msg(struct ssv_softc *sc, struct ipc_e2a_msg *msg)
|
|
{
|
|
if(_ssv_rx_check_msg(msg) == -1) {
|
|
SSV_LOG_ERR("unknown msg: %x, drop!!\n", msg->id);
|
|
return 0;
|
|
}
|
|
|
|
return sc->cmd_mgr.msgind(&sc->cmd_mgr, msg,
|
|
msg_hdlrs[MSG_T(msg->id)][MSG_I(msg->id)]);
|
|
}
|
|
|