OpenCloudOS-Kernel/drivers/net/wireless/libertas/cmd.c

1800 lines
44 KiB
C
Raw Normal View History

/**
* This file contains the handling of command.
* It prepares command and sends it to firmware when it is ready.
*/
#include <linux/kfifo.h>
#include <linux/sched.h>
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
#include <linux/slab.h>
#include <linux/if_arp.h>
#include "decl.h"
#include "cfg.h"
#include "cmd.h"
#define CAL_NF(nf) ((s32)(-(s32)(nf)))
#define CAL_RSSI(snr, nf) ((s32)((s32)(snr) + CAL_NF(nf)))
static struct cmd_ctrl_node *lbs_get_cmd_ctrl_node(struct lbs_private *priv);
/**
* @brief Simple callback that copies response back into command
*
* @param priv A pointer to struct lbs_private structure
* @param extra A pointer to the original command structure for which
* 'resp' is a response
* @param resp A pointer to the command response
*
* @return 0 on success, error on failure
*/
int lbs_cmd_copyback(struct lbs_private *priv, unsigned long extra,
struct cmd_header *resp)
{
struct cmd_header *buf = (void *)extra;
uint16_t copy_len;
copy_len = min(le16_to_cpu(buf->size), le16_to_cpu(resp->size));
memcpy(buf, resp, copy_len);
return 0;
}
EXPORT_SYMBOL_GPL(lbs_cmd_copyback);
/**
* @brief Simple callback that ignores the result. Use this if
* you just want to send a command to the hardware, but don't
* care for the result.
*
* @param priv ignored
* @param extra ignored
* @param resp ignored
*
* @return 0 for success
*/
static int lbs_cmd_async_callback(struct lbs_private *priv, unsigned long extra,
struct cmd_header *resp)
{
return 0;
}
/**
* @brief Checks whether a command is allowed in Power Save mode
*
* @param command the command ID
* @return 1 if allowed, 0 if not allowed
*/
static u8 is_command_allowed_in_ps(u16 cmd)
{
switch (cmd) {
case CMD_802_11_RSSI:
return 1;
case CMD_802_11_HOST_SLEEP_CFG:
return 1;
default:
break;
}
return 0;
}
/**
* @brief This function checks if the command is allowed.
*
* @param priv A pointer to lbs_private structure
* @return allowed or not allowed.
*/
static int lbs_is_cmd_allowed(struct lbs_private *priv)
{
int ret = 1;
lbs_deb_enter(LBS_DEB_CMD);
if (!priv->is_auto_deep_sleep_enabled) {
if (priv->is_deep_sleep) {
lbs_deb_cmd("command not allowed in deep sleep\n");
ret = 0;
}
}
lbs_deb_leave(LBS_DEB_CMD);
return ret;
}
/**
* @brief Updates the hardware details like MAC address and regulatory region
*
* @param priv A pointer to struct lbs_private structure
*
* @return 0 on success, error on failure
*/
int lbs_update_hw_spec(struct lbs_private *priv)
{
struct cmd_ds_get_hw_spec cmd;
int ret = -1;
u32 i;
lbs_deb_enter(LBS_DEB_CMD);
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
memcpy(cmd.permanentaddr, priv->current_addr, ETH_ALEN);
ret = lbs_cmd_with_response(priv, CMD_GET_HW_SPEC, &cmd);
if (ret)
goto out;
priv->fwcapinfo = le32_to_cpu(cmd.fwcapinfo);
/* The firmware release is in an interesting format: the patch
* level is in the most significant nibble ... so fix that: */
priv->fwrelease = le32_to_cpu(cmd.fwrelease);
priv->fwrelease = (priv->fwrelease << 8) |
(priv->fwrelease >> 24 & 0xff);
/* Some firmware capabilities:
* CF card firmware 5.0.16p0: cap 0x00000303
* USB dongle firmware 5.110.17p2: cap 0x00000303
*/
lbs_pr_info("%pM, fw %u.%u.%up%u, cap 0x%08x\n",
cmd.permanentaddr,
priv->fwrelease >> 24 & 0xff,
priv->fwrelease >> 16 & 0xff,
priv->fwrelease >> 8 & 0xff,
priv->fwrelease & 0xff,
priv->fwcapinfo);
lbs_deb_cmd("GET_HW_SPEC: hardware interface 0x%x, hardware spec 0x%04x\n",
cmd.hwifversion, cmd.version);
/* Clamp region code to 8-bit since FW spec indicates that it should
* only ever be 8-bit, even though the field size is 16-bit. Some firmware
* returns non-zero high 8 bits here.
*
* Firmware version 4.0.102 used in CF8381 has region code shifted. We
* need to check for this problem and handle it properly.
*/
if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V4)
priv->regioncode = (le16_to_cpu(cmd.regioncode) >> 8) & 0xFF;
else
priv->regioncode = le16_to_cpu(cmd.regioncode) & 0xFF;
for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) {
/* use the region code to search for the index */
if (priv->regioncode == lbs_region_code_to_index[i])
break;
}
/* if it's unidentified region code, use the default (USA) */
if (i >= MRVDRV_MAX_REGION_CODE) {
priv->regioncode = 0x10;
lbs_pr_info("unidentified region code; using the default (USA)\n");
}
if (priv->current_addr[0] == 0xff)
memmove(priv->current_addr, cmd.permanentaddr, ETH_ALEN);
memcpy(priv->dev->dev_addr, priv->current_addr, ETH_ALEN);
if (priv->mesh_dev)
memcpy(priv->mesh_dev->dev_addr, priv->current_addr, ETH_ALEN);
out:
lbs_deb_leave(LBS_DEB_CMD);
return ret;
}
static int lbs_ret_host_sleep_cfg(struct lbs_private *priv, unsigned long dummy,
struct cmd_header *resp)
{
lbs_deb_enter(LBS_DEB_CMD);
if (priv->is_host_sleep_activated) {
priv->is_host_sleep_configured = 0;
if (priv->psstate == PS_STATE_FULL_POWER) {
priv->is_host_sleep_activated = 0;
wake_up_interruptible(&priv->host_sleep_q);
}
} else {
priv->is_host_sleep_configured = 1;
}
lbs_deb_leave(LBS_DEB_CMD);
return 0;
}
int lbs_host_sleep_cfg(struct lbs_private *priv, uint32_t criteria,
struct wol_config *p_wol_config)
{
struct cmd_ds_host_sleep cmd_config;
int ret;
cmd_config.hdr.size = cpu_to_le16(sizeof(cmd_config));
cmd_config.criteria = cpu_to_le32(criteria);
cmd_config.gpio = priv->wol_gpio;
cmd_config.gap = priv->wol_gap;
if (p_wol_config != NULL)
memcpy((uint8_t *)&cmd_config.wol_conf, (uint8_t *)p_wol_config,
sizeof(struct wol_config));
else
cmd_config.wol_conf.action = CMD_ACT_ACTION_NONE;
ret = __lbs_cmd(priv, CMD_802_11_HOST_SLEEP_CFG, &cmd_config.hdr,
le16_to_cpu(cmd_config.hdr.size),
lbs_ret_host_sleep_cfg, 0);
if (!ret) {
if (p_wol_config)
memcpy((uint8_t *) p_wol_config,
(uint8_t *)&cmd_config.wol_conf,
sizeof(struct wol_config));
} else {
lbs_pr_info("HOST_SLEEP_CFG failed %d\n", ret);
}
return ret;
}
EXPORT_SYMBOL_GPL(lbs_host_sleep_cfg);
static int lbs_cmd_802_11_ps_mode(struct cmd_ds_command *cmd,
u16 cmd_action)
{
struct cmd_ds_802_11_ps_mode *psm = &cmd->params.psmode;
lbs_deb_enter(LBS_DEB_CMD);
cmd->command = cpu_to_le16(CMD_802_11_PS_MODE);
cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_ps_mode) +
sizeof(struct cmd_header));
psm->action = cpu_to_le16(cmd_action);
psm->multipledtim = 0;
switch (cmd_action) {
case CMD_SUBCMD_ENTER_PS:
lbs_deb_cmd("PS command:" "SubCode- Enter PS\n");
psm->locallisteninterval = 0;
psm->nullpktinterval = 0;
psm->multipledtim =
cpu_to_le16(MRVDRV_DEFAULT_MULTIPLE_DTIM);
break;
case CMD_SUBCMD_EXIT_PS:
lbs_deb_cmd("PS command:" "SubCode- Exit PS\n");
break;
case CMD_SUBCMD_SLEEP_CONFIRMED:
lbs_deb_cmd("PS command: SubCode- sleep confirm\n");
break;
default:
break;
}
lbs_deb_leave(LBS_DEB_CMD);
return 0;
}
int lbs_cmd_802_11_sleep_params(struct lbs_private *priv, uint16_t cmd_action,
struct sleep_params *sp)
{
struct cmd_ds_802_11_sleep_params cmd;
int ret;
lbs_deb_enter(LBS_DEB_CMD);
if (cmd_action == CMD_ACT_GET) {
memset(&cmd, 0, sizeof(cmd));
} else {
cmd.error = cpu_to_le16(sp->sp_error);
cmd.offset = cpu_to_le16(sp->sp_offset);
cmd.stabletime = cpu_to_le16(sp->sp_stabletime);
cmd.calcontrol = sp->sp_calcontrol;
cmd.externalsleepclk = sp->sp_extsleepclk;
cmd.reserved = cpu_to_le16(sp->sp_reserved);
}
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(cmd_action);
ret = lbs_cmd_with_response(priv, CMD_802_11_SLEEP_PARAMS, &cmd);
if (!ret) {
lbs_deb_cmd("error 0x%x, offset 0x%x, stabletime 0x%x, "
"calcontrol 0x%x extsleepclk 0x%x\n",
le16_to_cpu(cmd.error), le16_to_cpu(cmd.offset),
le16_to_cpu(cmd.stabletime), cmd.calcontrol,
cmd.externalsleepclk);
sp->sp_error = le16_to_cpu(cmd.error);
sp->sp_offset = le16_to_cpu(cmd.offset);
sp->sp_stabletime = le16_to_cpu(cmd.stabletime);
sp->sp_calcontrol = cmd.calcontrol;
sp->sp_extsleepclk = cmd.externalsleepclk;
sp->sp_reserved = le16_to_cpu(cmd.reserved);
}
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return 0;
}
static int lbs_wait_for_ds_awake(struct lbs_private *priv)
{
int ret = 0;
lbs_deb_enter(LBS_DEB_CMD);
if (priv->is_deep_sleep) {
if (!wait_event_interruptible_timeout(priv->ds_awake_q,
!priv->is_deep_sleep, (10 * HZ))) {
lbs_pr_err("ds_awake_q: timer expired\n");
ret = -1;
}
}
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return ret;
}
int lbs_set_deep_sleep(struct lbs_private *priv, int deep_sleep)
{
int ret = 0;
lbs_deb_enter(LBS_DEB_CMD);
if (deep_sleep) {
if (priv->is_deep_sleep != 1) {
lbs_deb_cmd("deep sleep: sleep\n");
BUG_ON(!priv->enter_deep_sleep);
ret = priv->enter_deep_sleep(priv);
if (!ret) {
netif_stop_queue(priv->dev);
netif_carrier_off(priv->dev);
}
} else {
lbs_pr_err("deep sleep: already enabled\n");
}
} else {
if (priv->is_deep_sleep) {
lbs_deb_cmd("deep sleep: wakeup\n");
BUG_ON(!priv->exit_deep_sleep);
ret = priv->exit_deep_sleep(priv);
if (!ret) {
ret = lbs_wait_for_ds_awake(priv);
if (ret)
lbs_pr_err("deep sleep: wakeup"
"failed\n");
}
}
}
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return ret;
}
static int lbs_ret_host_sleep_activate(struct lbs_private *priv,
unsigned long dummy,
struct cmd_header *cmd)
{
lbs_deb_enter(LBS_DEB_FW);
priv->is_host_sleep_activated = 1;
wake_up_interruptible(&priv->host_sleep_q);
lbs_deb_leave(LBS_DEB_FW);
return 0;
}
int lbs_set_host_sleep(struct lbs_private *priv, int host_sleep)
{
struct cmd_header cmd;
int ret = 0;
uint32_t criteria = EHS_REMOVE_WAKEUP;
lbs_deb_enter(LBS_DEB_CMD);
if (host_sleep) {
if (priv->is_host_sleep_activated != 1) {
memset(&cmd, 0, sizeof(cmd));
ret = lbs_host_sleep_cfg(priv, priv->wol_criteria,
(struct wol_config *)NULL);
if (ret) {
lbs_pr_info("Host sleep configuration failed: "
"%d\n", ret);
return ret;
}
if (priv->psstate == PS_STATE_FULL_POWER) {
ret = __lbs_cmd(priv,
CMD_802_11_HOST_SLEEP_ACTIVATE,
&cmd,
sizeof(cmd),
lbs_ret_host_sleep_activate, 0);
if (ret)
lbs_pr_info("HOST_SLEEP_ACTIVATE "
"failed: %d\n", ret);
}
if (!wait_event_interruptible_timeout(
priv->host_sleep_q,
priv->is_host_sleep_activated,
(10 * HZ))) {
lbs_pr_err("host_sleep_q: timer expired\n");
ret = -1;
}
} else {
lbs_pr_err("host sleep: already enabled\n");
}
} else {
if (priv->is_host_sleep_activated)
ret = lbs_host_sleep_cfg(priv, criteria,
(struct wol_config *)NULL);
}
return ret;
}
/**
* @brief Set an SNMP MIB value
*
* @param priv A pointer to struct lbs_private structure
* @param oid The OID to set in the firmware
* @param val Value to set the OID to
*
* @return 0 on success, error on failure
*/
int lbs_set_snmp_mib(struct lbs_private *priv, u32 oid, u16 val)
{
struct cmd_ds_802_11_snmp_mib cmd;
int ret;
lbs_deb_enter(LBS_DEB_CMD);
memset(&cmd, 0, sizeof (cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_ACT_SET);
cmd.oid = cpu_to_le16((u16) oid);
switch (oid) {
case SNMP_MIB_OID_BSS_TYPE:
cmd.bufsize = cpu_to_le16(sizeof(u8));
cmd.value[0] = val;
break;
case SNMP_MIB_OID_11D_ENABLE:
case SNMP_MIB_OID_FRAG_THRESHOLD:
case SNMP_MIB_OID_RTS_THRESHOLD:
case SNMP_MIB_OID_SHORT_RETRY_LIMIT:
case SNMP_MIB_OID_LONG_RETRY_LIMIT:
cmd.bufsize = cpu_to_le16(sizeof(u16));
*((__le16 *)(&cmd.value)) = cpu_to_le16(val);
break;
default:
lbs_deb_cmd("SNMP_CMD: (set) unhandled OID 0x%x\n", oid);
ret = -EINVAL;
goto out;
}
lbs_deb_cmd("SNMP_CMD: (set) oid 0x%x, oid size 0x%x, value 0x%x\n",
le16_to_cpu(cmd.oid), le16_to_cpu(cmd.bufsize), val);
ret = lbs_cmd_with_response(priv, CMD_802_11_SNMP_MIB, &cmd);
out:
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return ret;
}
/**
* @brief Get an SNMP MIB value
*
* @param priv A pointer to struct lbs_private structure
* @param oid The OID to retrieve from the firmware
* @param out_val Location for the returned value
*
* @return 0 on success, error on failure
*/
int lbs_get_snmp_mib(struct lbs_private *priv, u32 oid, u16 *out_val)
{
struct cmd_ds_802_11_snmp_mib cmd;
int ret;
lbs_deb_enter(LBS_DEB_CMD);
memset(&cmd, 0, sizeof (cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_ACT_GET);
cmd.oid = cpu_to_le16(oid);
ret = lbs_cmd_with_response(priv, CMD_802_11_SNMP_MIB, &cmd);
if (ret)
goto out;
switch (le16_to_cpu(cmd.bufsize)) {
case sizeof(u8):
*out_val = cmd.value[0];
break;
case sizeof(u16):
*out_val = le16_to_cpu(*((__le16 *)(&cmd.value)));
break;
default:
lbs_deb_cmd("SNMP_CMD: (get) unhandled OID 0x%x size %d\n",
oid, le16_to_cpu(cmd.bufsize));
break;
}
out:
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return ret;
}
/**
* @brief Get the min, max, and current TX power
*
* @param priv A pointer to struct lbs_private structure
* @param curlevel Current power level in dBm
* @param minlevel Minimum supported power level in dBm (optional)
* @param maxlevel Maximum supported power level in dBm (optional)
*
* @return 0 on success, error on failure
*/
int lbs_get_tx_power(struct lbs_private *priv, s16 *curlevel, s16 *minlevel,
s16 *maxlevel)
{
struct cmd_ds_802_11_rf_tx_power cmd;
int ret;
lbs_deb_enter(LBS_DEB_CMD);
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_ACT_GET);
ret = lbs_cmd_with_response(priv, CMD_802_11_RF_TX_POWER, &cmd);
if (ret == 0) {
*curlevel = le16_to_cpu(cmd.curlevel);
if (minlevel)
*minlevel = cmd.minlevel;
if (maxlevel)
*maxlevel = cmd.maxlevel;
}
lbs_deb_leave(LBS_DEB_CMD);
return ret;
}
/**
* @brief Set the TX power
*
* @param priv A pointer to struct lbs_private structure
* @param dbm The desired power level in dBm
*
* @return 0 on success, error on failure
*/
int lbs_set_tx_power(struct lbs_private *priv, s16 dbm)
{
struct cmd_ds_802_11_rf_tx_power cmd;
int ret;
lbs_deb_enter(LBS_DEB_CMD);
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_ACT_SET);
cmd.curlevel = cpu_to_le16(dbm);
lbs_deb_cmd("SET_RF_TX_POWER: %d dBm\n", dbm);
ret = lbs_cmd_with_response(priv, CMD_802_11_RF_TX_POWER, &cmd);
lbs_deb_leave(LBS_DEB_CMD);
return ret;
}
/**
* @brief Enable or disable monitor mode (only implemented on OLPC usb8388 FW)
*
* @param priv A pointer to struct lbs_private structure
* @param enable 1 to enable monitor mode, 0 to disable
*
* @return 0 on success, error on failure
*/
int lbs_set_monitor_mode(struct lbs_private *priv, int enable)
{
struct cmd_ds_802_11_monitor_mode cmd;
int ret;
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_ACT_SET);
if (enable)
cmd.mode = cpu_to_le16(0x1);
lbs_deb_cmd("SET_MONITOR_MODE: %d\n", enable);
ret = lbs_cmd_with_response(priv, CMD_802_11_MONITOR_MODE, &cmd);
if (ret == 0) {
priv->dev->type = enable ? ARPHRD_IEEE80211_RADIOTAP :
ARPHRD_ETHER;
}
lbs_deb_leave(LBS_DEB_CMD);
return ret;
}
/**
* @brief Get the radio channel
*
* @param priv A pointer to struct lbs_private structure
*
* @return The channel on success, error on failure
*/
static int lbs_get_channel(struct lbs_private *priv)
{
struct cmd_ds_802_11_rf_channel cmd;
int ret = 0;
lbs_deb_enter(LBS_DEB_CMD);
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_GET);
ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd);
if (ret)
goto out;
ret = le16_to_cpu(cmd.channel);
lbs_deb_cmd("current radio channel is %d\n", ret);
out:
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return ret;
}
int lbs_update_channel(struct lbs_private *priv)
{
int ret;
/* the channel in f/w could be out of sync; get the current channel */
lbs_deb_enter(LBS_DEB_ASSOC);
ret = lbs_get_channel(priv);
if (ret > 0) {
priv->channel = ret;
ret = 0;
}
lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret);
return ret;
}
/**
* @brief Set the radio channel
*
* @param priv A pointer to struct lbs_private structure
* @param channel The desired channel, or 0 to clear a locked channel
*
* @return 0 on success, error on failure
*/
int lbs_set_channel(struct lbs_private *priv, u8 channel)
{
struct cmd_ds_802_11_rf_channel cmd;
#ifdef DEBUG
u8 old_channel = priv->channel;
#endif
int ret = 0;
lbs_deb_enter(LBS_DEB_CMD);
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_SET);
cmd.channel = cpu_to_le16(channel);
ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd);
if (ret)
goto out;
priv->channel = (uint8_t) le16_to_cpu(cmd.channel);
lbs_deb_cmd("channel switch from %d to %d\n", old_channel,
priv->channel);
out:
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return ret;
}
/**
* @brief Get current RSSI and noise floor
*
* @param priv A pointer to struct lbs_private structure
* @param rssi On successful return, signal level in mBm
*
* @return The channel on success, error on failure
*/
int lbs_get_rssi(struct lbs_private *priv, s8 *rssi, s8 *nf)
{
struct cmd_ds_802_11_rssi cmd;
int ret = 0;
lbs_deb_enter(LBS_DEB_CMD);
BUG_ON(rssi == NULL);
BUG_ON(nf == NULL);
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
/* Average SNR over last 8 beacons */
cmd.n_or_snr = cpu_to_le16(8);
ret = lbs_cmd_with_response(priv, CMD_802_11_RSSI, &cmd);
if (ret == 0) {
*nf = CAL_NF(le16_to_cpu(cmd.nf));
*rssi = CAL_RSSI(le16_to_cpu(cmd.n_or_snr), le16_to_cpu(cmd.nf));
}
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return ret;
}
static int lbs_cmd_reg_access(struct cmd_ds_command *cmdptr,
u8 cmd_action, void *pdata_buf)
{
struct lbs_offset_value *offval;
lbs_deb_enter(LBS_DEB_CMD);
offval = (struct lbs_offset_value *)pdata_buf;
switch (le16_to_cpu(cmdptr->command)) {
case CMD_MAC_REG_ACCESS:
{
struct cmd_ds_mac_reg_access *macreg;
cmdptr->size =
cpu_to_le16(sizeof (struct cmd_ds_mac_reg_access)
+ sizeof(struct cmd_header));
macreg =
(struct cmd_ds_mac_reg_access *)&cmdptr->params.
macreg;
macreg->action = cpu_to_le16(cmd_action);
macreg->offset = cpu_to_le16((u16) offval->offset);
macreg->value = cpu_to_le32(offval->value);
break;
}
case CMD_BBP_REG_ACCESS:
{
struct cmd_ds_bbp_reg_access *bbpreg;
cmdptr->size =
cpu_to_le16(sizeof
(struct cmd_ds_bbp_reg_access)
+ sizeof(struct cmd_header));
bbpreg =
(struct cmd_ds_bbp_reg_access *)&cmdptr->params.
bbpreg;
bbpreg->action = cpu_to_le16(cmd_action);
bbpreg->offset = cpu_to_le16((u16) offval->offset);
bbpreg->value = (u8) offval->value;
break;
}
case CMD_RF_REG_ACCESS:
{
struct cmd_ds_rf_reg_access *rfreg;
cmdptr->size =
cpu_to_le16(sizeof
(struct cmd_ds_rf_reg_access) +
sizeof(struct cmd_header));
rfreg =
(struct cmd_ds_rf_reg_access *)&cmdptr->params.
rfreg;
rfreg->action = cpu_to_le16(cmd_action);
rfreg->offset = cpu_to_le16((u16) offval->offset);
rfreg->value = (u8) offval->value;
break;
}
default:
break;
}
lbs_deb_leave(LBS_DEB_CMD);
return 0;
}
static void lbs_queue_cmd(struct lbs_private *priv,
struct cmd_ctrl_node *cmdnode)
{
unsigned long flags;
int addtail = 1;
lbs_deb_enter(LBS_DEB_HOST);
if (!cmdnode) {
lbs_deb_host("QUEUE_CMD: cmdnode is NULL\n");
goto done;
}
if (!cmdnode->cmdbuf->size) {
lbs_deb_host("DNLD_CMD: cmd size is zero\n");
goto done;
}
cmdnode->result = 0;
/* Exit_PS command needs to be queued in the header always. */
if (le16_to_cpu(cmdnode->cmdbuf->command) == CMD_802_11_PS_MODE) {
struct cmd_ds_802_11_ps_mode *psm = (void *) &cmdnode->cmdbuf[1];
if (psm->action == cpu_to_le16(CMD_SUBCMD_EXIT_PS)) {
if (priv->psstate != PS_STATE_FULL_POWER)
addtail = 0;
}
}
if (le16_to_cpu(cmdnode->cmdbuf->command) ==
CMD_802_11_WAKEUP_CONFIRM)
addtail = 0;
spin_lock_irqsave(&priv->driver_lock, flags);
if (addtail)
list_add_tail(&cmdnode->list, &priv->cmdpendingq);
else
list_add(&cmdnode->list, &priv->cmdpendingq);
spin_unlock_irqrestore(&priv->driver_lock, flags);
lbs_deb_host("QUEUE_CMD: inserted command 0x%04x into cmdpendingq\n",
le16_to_cpu(cmdnode->cmdbuf->command));
done:
lbs_deb_leave(LBS_DEB_HOST);
}
static void lbs_submit_command(struct lbs_private *priv,
struct cmd_ctrl_node *cmdnode)
{
unsigned long flags;
struct cmd_header *cmd;
uint16_t cmdsize;
uint16_t command;
int timeo = 3 * HZ;
int ret;
lbs_deb_enter(LBS_DEB_HOST);
cmd = cmdnode->cmdbuf;
spin_lock_irqsave(&priv->driver_lock, flags);
priv->cur_cmd = cmdnode;
priv->cur_cmd_retcode = 0;
spin_unlock_irqrestore(&priv->driver_lock, flags);
cmdsize = le16_to_cpu(cmd->size);
command = le16_to_cpu(cmd->command);
/* These commands take longer */
if (command == CMD_802_11_SCAN || command == CMD_802_11_ASSOCIATE)
timeo = 5 * HZ;
lbs_deb_cmd("DNLD_CMD: command 0x%04x, seq %d, size %d\n",
command, le16_to_cpu(cmd->seqnum), cmdsize);
lbs_deb_hex(LBS_DEB_CMD, "DNLD_CMD", (void *) cmdnode->cmdbuf, cmdsize);
ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) cmd, cmdsize);
if (ret) {
lbs_pr_info("DNLD_CMD: hw_host_to_card failed: %d\n", ret);
/* Let the timer kick in and retry, and potentially reset
the whole thing if the condition persists */
timeo = HZ/4;
}
if (command == CMD_802_11_DEEP_SLEEP) {
if (priv->is_auto_deep_sleep_enabled) {
priv->wakeup_dev_required = 1;
priv->dnld_sent = 0;
}
priv->is_deep_sleep = 1;
lbs_complete_command(priv, cmdnode, 0);
} else {
/* Setup the timer after transmit command */
mod_timer(&priv->command_timer, jiffies + timeo);
}
lbs_deb_leave(LBS_DEB_HOST);
}
/**
* This function inserts command node to cmdfreeq
* after cleans it. Requires priv->driver_lock held.
*/
static void __lbs_cleanup_and_insert_cmd(struct lbs_private *priv,
struct cmd_ctrl_node *cmdnode)
{
lbs_deb_enter(LBS_DEB_HOST);
if (!cmdnode)
goto out;
cmdnode->callback = NULL;
cmdnode->callback_arg = 0;
memset(cmdnode->cmdbuf, 0, LBS_CMD_BUFFER_SIZE);
list_add_tail(&cmdnode->list, &priv->cmdfreeq);
out:
lbs_deb_leave(LBS_DEB_HOST);
}
static void lbs_cleanup_and_insert_cmd(struct lbs_private *priv,
struct cmd_ctrl_node *ptempcmd)
{
unsigned long flags;
spin_lock_irqsave(&priv->driver_lock, flags);
__lbs_cleanup_and_insert_cmd(priv, ptempcmd);
spin_unlock_irqrestore(&priv->driver_lock, flags);
}
void lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd,
int result)
{
if (cmd == priv->cur_cmd)
priv->cur_cmd_retcode = result;
cmd->result = result;
cmd->cmdwaitqwoken = 1;
wake_up_interruptible(&cmd->cmdwait_q);
if (!cmd->callback || cmd->callback == lbs_cmd_async_callback)
__lbs_cleanup_and_insert_cmd(priv, cmd);
priv->cur_cmd = NULL;
}
int lbs_set_radio(struct lbs_private *priv, u8 preamble, u8 radio_on)
{
struct cmd_ds_802_11_radio_control cmd;
int ret = -EINVAL;
lbs_deb_enter(LBS_DEB_CMD);
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_ACT_SET);
/* Only v8 and below support setting the preamble */
if (priv->fwrelease < 0x09000000) {
switch (preamble) {
case RADIO_PREAMBLE_SHORT:
case RADIO_PREAMBLE_AUTO:
case RADIO_PREAMBLE_LONG:
cmd.control = cpu_to_le16(preamble);
break;
default:
goto out;
}
}
if (radio_on)
cmd.control |= cpu_to_le16(0x1);
else {
cmd.control &= cpu_to_le16(~0x1);
priv->txpower_cur = 0;
}
lbs_deb_cmd("RADIO_CONTROL: radio %s, preamble %d\n",
radio_on ? "ON" : "OFF", preamble);
priv->radio_on = radio_on;
ret = lbs_cmd_with_response(priv, CMD_802_11_RADIO_CONTROL, &cmd);
out:
lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret);
return ret;
}
void lbs_set_mac_control(struct lbs_private *priv)
{
struct cmd_ds_mac_control cmd;
lbs_deb_enter(LBS_DEB_CMD);
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(priv->mac_control);
cmd.reserved = 0;
lbs_cmd_async(priv, CMD_MAC_CONTROL, &cmd.hdr, sizeof(cmd));
lbs_deb_leave(LBS_DEB_CMD);
}
/**
* @brief This function implements command CMD_802_11D_DOMAIN_INFO
* @param priv pointer to struct lbs_private
* @param cmd pointer to cmd buffer
* @param cmdno cmd ID
* @param cmdOption cmd action
* @return 0
*/
int lbs_cmd_802_11d_domain_info(struct lbs_private *priv,
struct cmd_ds_command *cmd,
u16 cmdoption)
{
struct cmd_ds_802_11d_domain_info *pdomaininfo =
&cmd->params.domaininfo;
struct mrvl_ie_domain_param_set *domain = &pdomaininfo->domain;
u8 nr_triplet = priv->domain_reg.no_triplet;
lbs_deb_enter(LBS_DEB_11D);
lbs_deb_11d("nr_triplet=%x\n", nr_triplet);
pdomaininfo->action = cpu_to_le16(cmdoption);
if (cmdoption == CMD_ACT_GET) {
cmd->size = cpu_to_le16(sizeof(pdomaininfo->action) +
sizeof(struct cmd_header));
lbs_deb_hex(LBS_DEB_11D, "802_11D_DOMAIN_INFO", (u8 *) cmd,
le16_to_cpu(cmd->size));
goto done;
}
domain->header.type = cpu_to_le16(TLV_TYPE_DOMAIN);
memcpy(domain->countrycode, priv->domain_reg.country_code,
sizeof(domain->countrycode));
domain->header.len = cpu_to_le16(nr_triplet
* sizeof(struct ieee80211_country_ie_triplet)
+ sizeof(domain->countrycode));
if (nr_triplet) {
memcpy(domain->triplet, priv->domain_reg.triplet,
nr_triplet *
sizeof(struct ieee80211_country_ie_triplet));
cmd->size = cpu_to_le16(sizeof(pdomaininfo->action) +
le16_to_cpu(domain->header.len) +
sizeof(struct mrvl_ie_header) +
sizeof(struct cmd_header));
} else {
cmd->size = cpu_to_le16(sizeof(pdomaininfo->action) +
sizeof(struct cmd_header));
}
lbs_deb_hex(LBS_DEB_11D, "802_11D_DOMAIN_INFO", (u8 *) cmd,
le16_to_cpu(cmd->size));
done:
lbs_deb_enter(LBS_DEB_11D);
return 0;
}
/**
* @brief This function prepare the command before send to firmware.
*
* @param priv A pointer to struct lbs_private structure
* @param cmd_no command number
* @param cmd_action command action: GET or SET
* @param wait_option wait option: wait response or not
* @param cmd_oid cmd oid: treated as sub command
* @param pdata_buf A pointer to informaion buffer
* @return 0 or -1
*/
int lbs_prepare_and_send_command(struct lbs_private *priv,
u16 cmd_no,
u16 cmd_action,
u16 wait_option, u32 cmd_oid, void *pdata_buf)
{
int ret = 0;
struct cmd_ctrl_node *cmdnode;
struct cmd_ds_command *cmdptr;
unsigned long flags;
lbs_deb_enter(LBS_DEB_HOST);
if (!priv) {
lbs_deb_host("PREP_CMD: priv is NULL\n");
ret = -1;
goto done;
}
if (priv->surpriseremoved) {
lbs_deb_host("PREP_CMD: card removed\n");
ret = -1;
goto done;
}
if (!lbs_is_cmd_allowed(priv)) {
ret = -EBUSY;
goto done;
}
cmdnode = lbs_get_cmd_ctrl_node(priv);
if (cmdnode == NULL) {
lbs_deb_host("PREP_CMD: cmdnode is NULL\n");
/* Wake up main thread to execute next command */
wake_up_interruptible(&priv->waitq);
ret = -1;
goto done;
}
cmdnode->callback = NULL;
cmdnode->callback_arg = (unsigned long)pdata_buf;
cmdptr = (struct cmd_ds_command *)cmdnode->cmdbuf;
lbs_deb_host("PREP_CMD: command 0x%04x\n", cmd_no);
/* Set sequence number, command and INT option */
priv->seqnum++;
cmdptr->seqnum = cpu_to_le16(priv->seqnum);
cmdptr->command = cpu_to_le16(cmd_no);
cmdptr->result = 0;
switch (cmd_no) {
case CMD_802_11_PS_MODE:
ret = lbs_cmd_802_11_ps_mode(cmdptr, cmd_action);
break;
case CMD_MAC_REG_ACCESS:
case CMD_BBP_REG_ACCESS:
case CMD_RF_REG_ACCESS:
ret = lbs_cmd_reg_access(cmdptr, cmd_action, pdata_buf);
break;
case CMD_802_11_SET_AFC:
case CMD_802_11_GET_AFC:
cmdptr->command = cpu_to_le16(cmd_no);
cmdptr->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_afc) +
sizeof(struct cmd_header));
memmove(&cmdptr->params.afc,
pdata_buf, sizeof(struct cmd_ds_802_11_afc));
ret = 0;
goto done;
case CMD_802_11D_DOMAIN_INFO:
cmdptr->command = cpu_to_le16(cmd_no);
ret = lbs_cmd_802_11d_domain_info(priv, cmdptr, cmd_action);
break;
case CMD_802_11_TPC_CFG:
cmdptr->command = cpu_to_le16(CMD_802_11_TPC_CFG);
cmdptr->size =
cpu_to_le16(sizeof(struct cmd_ds_802_11_tpc_cfg) +
sizeof(struct cmd_header));
memmove(&cmdptr->params.tpccfg,
pdata_buf, sizeof(struct cmd_ds_802_11_tpc_cfg));
ret = 0;
break;
#ifdef CONFIG_LIBERTAS_MESH
case CMD_BT_ACCESS:
ret = lbs_cmd_bt_access(cmdptr, cmd_action, pdata_buf);
break;
case CMD_FWT_ACCESS:
ret = lbs_cmd_fwt_access(cmdptr, cmd_action, pdata_buf);
break;
#endif
case CMD_802_11_BEACON_CTRL:
ret = lbs_cmd_bcn_ctrl(priv, cmdptr, cmd_action);
break;
case CMD_802_11_DEEP_SLEEP:
cmdptr->command = cpu_to_le16(CMD_802_11_DEEP_SLEEP);
cmdptr->size = cpu_to_le16(sizeof(struct cmd_header));
break;
default:
lbs_pr_err("PREP_CMD: unknown command 0x%04x\n", cmd_no);
ret = -1;
break;
}
/* return error, since the command preparation failed */
if (ret != 0) {
lbs_deb_host("PREP_CMD: command preparation failed\n");
lbs_cleanup_and_insert_cmd(priv, cmdnode);
ret = -1;
goto done;
}
cmdnode->cmdwaitqwoken = 0;
lbs_queue_cmd(priv, cmdnode);
wake_up_interruptible(&priv->waitq);
if (wait_option & CMD_OPTION_WAITFORRSP) {
lbs_deb_host("PREP_CMD: wait for response\n");
might_sleep();
wait_event_interruptible(cmdnode->cmdwait_q,
cmdnode->cmdwaitqwoken);
}
spin_lock_irqsave(&priv->driver_lock, flags);
if (priv->cur_cmd_retcode) {
lbs_deb_host("PREP_CMD: command failed with return code %d\n",
priv->cur_cmd_retcode);
priv->cur_cmd_retcode = 0;
ret = -1;
}
spin_unlock_irqrestore(&priv->driver_lock, flags);
done:
lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret);
return ret;
}
/**
* @brief This function allocates the command buffer and link
* it to command free queue.
*
* @param priv A pointer to struct lbs_private structure
* @return 0 or -1
*/
int lbs_allocate_cmd_buffer(struct lbs_private *priv)
{
int ret = 0;
u32 bufsize;
u32 i;
struct cmd_ctrl_node *cmdarray;
lbs_deb_enter(LBS_DEB_HOST);
/* Allocate and initialize the command array */
bufsize = sizeof(struct cmd_ctrl_node) * LBS_NUM_CMD_BUFFERS;
if (!(cmdarray = kzalloc(bufsize, GFP_KERNEL))) {
lbs_deb_host("ALLOC_CMD_BUF: tempcmd_array is NULL\n");
ret = -1;
goto done;
}
priv->cmd_array = cmdarray;
/* Allocate and initialize each command buffer in the command array */
for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) {
cmdarray[i].cmdbuf = kzalloc(LBS_CMD_BUFFER_SIZE, GFP_KERNEL);
if (!cmdarray[i].cmdbuf) {
lbs_deb_host("ALLOC_CMD_BUF: ptempvirtualaddr is NULL\n");
ret = -1;
goto done;
}
}
for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) {
init_waitqueue_head(&cmdarray[i].cmdwait_q);
lbs_cleanup_and_insert_cmd(priv, &cmdarray[i]);
}
ret = 0;
done:
lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret);
return ret;
}
/**
* @brief This function frees the command buffer.
*
* @param priv A pointer to struct lbs_private structure
* @return 0 or -1
*/
int lbs_free_cmd_buffer(struct lbs_private *priv)
{
struct cmd_ctrl_node *cmdarray;
unsigned int i;
lbs_deb_enter(LBS_DEB_HOST);
/* need to check if cmd array is allocated or not */
if (priv->cmd_array == NULL) {
lbs_deb_host("FREE_CMD_BUF: cmd_array is NULL\n");
goto done;
}
cmdarray = priv->cmd_array;
/* Release shared memory buffers */
for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) {
if (cmdarray[i].cmdbuf) {
kfree(cmdarray[i].cmdbuf);
cmdarray[i].cmdbuf = NULL;
}
}
/* Release cmd_ctrl_node */
if (priv->cmd_array) {
kfree(priv->cmd_array);
priv->cmd_array = NULL;
}
done:
lbs_deb_leave(LBS_DEB_HOST);
return 0;
}
/**
* @brief This function gets a free command node if available in
* command free queue.
*
* @param priv A pointer to struct lbs_private structure
* @return cmd_ctrl_node A pointer to cmd_ctrl_node structure or NULL
*/
static struct cmd_ctrl_node *lbs_get_cmd_ctrl_node(struct lbs_private *priv)
{
struct cmd_ctrl_node *tempnode;
unsigned long flags;
lbs_deb_enter(LBS_DEB_HOST);
if (!priv)
return NULL;
spin_lock_irqsave(&priv->driver_lock, flags);
if (!list_empty(&priv->cmdfreeq)) {
tempnode = list_first_entry(&priv->cmdfreeq,
struct cmd_ctrl_node, list);
list_del(&tempnode->list);
} else {
lbs_deb_host("GET_CMD_NODE: cmd_ctrl_node is not available\n");
tempnode = NULL;
}
spin_unlock_irqrestore(&priv->driver_lock, flags);
lbs_deb_leave(LBS_DEB_HOST);
return tempnode;
}
/**
* @brief This function executes next command in command
* pending queue. It will put firmware back to PS mode
* if applicable.
*
* @param priv A pointer to struct lbs_private structure
* @return 0 or -1
*/
int lbs_execute_next_command(struct lbs_private *priv)
{
struct cmd_ctrl_node *cmdnode = NULL;
struct cmd_header *cmd;
unsigned long flags;
int ret = 0;
/* Debug group is LBS_DEB_THREAD and not LBS_DEB_HOST, because the
* only caller to us is lbs_thread() and we get even when a
* data packet is received */
lbs_deb_enter(LBS_DEB_THREAD);
spin_lock_irqsave(&priv->driver_lock, flags);
if (priv->cur_cmd) {
lbs_pr_alert( "EXEC_NEXT_CMD: already processing command!\n");
spin_unlock_irqrestore(&priv->driver_lock, flags);
ret = -1;
goto done;
}
if (!list_empty(&priv->cmdpendingq)) {
cmdnode = list_first_entry(&priv->cmdpendingq,
struct cmd_ctrl_node, list);
}
spin_unlock_irqrestore(&priv->driver_lock, flags);
if (cmdnode) {
cmd = cmdnode->cmdbuf;
if (is_command_allowed_in_ps(le16_to_cpu(cmd->command))) {
if ((priv->psstate == PS_STATE_SLEEP) ||
(priv->psstate == PS_STATE_PRE_SLEEP)) {
lbs_deb_host(
"EXEC_NEXT_CMD: cannot send cmd 0x%04x in psstate %d\n",
le16_to_cpu(cmd->command),
priv->psstate);
ret = -1;
goto done;
}
lbs_deb_host("EXEC_NEXT_CMD: OK to send command "
"0x%04x in psstate %d\n",
le16_to_cpu(cmd->command), priv->psstate);
} else if (priv->psstate != PS_STATE_FULL_POWER) {
/*
* 1. Non-PS command:
* Queue it. set needtowakeup to TRUE if current state
* is SLEEP, otherwise call lbs_ps_wakeup to send Exit_PS.
* 2. PS command but not Exit_PS:
* Ignore it.
* 3. PS command Exit_PS:
* Set needtowakeup to TRUE if current state is SLEEP,
* otherwise send this command down to firmware
* immediately.
*/
if (cmd->command != cpu_to_le16(CMD_802_11_PS_MODE)) {
/* Prepare to send Exit PS,
* this non PS command will be sent later */
if ((priv->psstate == PS_STATE_SLEEP)
|| (priv->psstate == PS_STATE_PRE_SLEEP)
) {
/* w/ new scheme, it will not reach here.
since it is blocked in main_thread. */
priv->needtowakeup = 1;
} else
lbs_ps_wakeup(priv, 0);
ret = 0;
goto done;
} else {
/*
* PS command. Ignore it if it is not Exit_PS.
* otherwise send it down immediately.
*/
struct cmd_ds_802_11_ps_mode *psm = (void *)&cmd[1];
lbs_deb_host(
"EXEC_NEXT_CMD: PS cmd, action 0x%02x\n",
psm->action);
if (psm->action !=
cpu_to_le16(CMD_SUBCMD_EXIT_PS)) {
lbs_deb_host(
"EXEC_NEXT_CMD: ignore ENTER_PS cmd\n");
list_del(&cmdnode->list);
spin_lock_irqsave(&priv->driver_lock, flags);
lbs_complete_command(priv, cmdnode, 0);
spin_unlock_irqrestore(&priv->driver_lock, flags);
ret = 0;
goto done;
}
if ((priv->psstate == PS_STATE_SLEEP) ||
(priv->psstate == PS_STATE_PRE_SLEEP)) {
lbs_deb_host(
"EXEC_NEXT_CMD: ignore EXIT_PS cmd in sleep\n");
list_del(&cmdnode->list);
spin_lock_irqsave(&priv->driver_lock, flags);
lbs_complete_command(priv, cmdnode, 0);
spin_unlock_irqrestore(&priv->driver_lock, flags);
priv->needtowakeup = 1;
ret = 0;
goto done;
}
lbs_deb_host(
"EXEC_NEXT_CMD: sending EXIT_PS\n");
}
}
list_del(&cmdnode->list);
lbs_deb_host("EXEC_NEXT_CMD: sending command 0x%04x\n",
le16_to_cpu(cmd->command));
lbs_submit_command(priv, cmdnode);
} else {
/*
* check if in power save mode, if yes, put the device back
* to PS mode
*/
#ifdef TODO
/*
* This was the old code for libertas+wext. Someone that
* understands this beast should re-code it in a sane way.
*
* I actually don't understand why this is related to WPA
* and to connection status, shouldn't powering should be
* independ of such things?
*/
if ((priv->psmode != LBS802_11POWERMODECAM) &&
(priv->psstate == PS_STATE_FULL_POWER) &&
((priv->connect_status == LBS_CONNECTED) ||
lbs_mesh_connected(priv))) {
if (priv->secinfo.WPAenabled ||
priv->secinfo.WPA2enabled) {
/* check for valid WPA group keys */
if (priv->wpa_mcast_key.len ||
priv->wpa_unicast_key.len) {
lbs_deb_host(
"EXEC_NEXT_CMD: WPA enabled and GTK_SET"
" go back to PS_SLEEP");
lbs_ps_sleep(priv, 0);
}
} else {
lbs_deb_host(
"EXEC_NEXT_CMD: cmdpendingq empty, "
"go back to PS_SLEEP");
lbs_ps_sleep(priv, 0);
}
}
#endif
}
ret = 0;
done:
lbs_deb_leave(LBS_DEB_THREAD);
return ret;
}
static void lbs_send_confirmsleep(struct lbs_private *priv)
{
unsigned long flags;
int ret;
lbs_deb_enter(LBS_DEB_HOST);
lbs_deb_hex(LBS_DEB_HOST, "sleep confirm", (u8 *) &confirm_sleep,
sizeof(confirm_sleep));
ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) &confirm_sleep,
sizeof(confirm_sleep));
if (ret) {
lbs_pr_alert("confirm_sleep failed\n");
goto out;
}
spin_lock_irqsave(&priv->driver_lock, flags);
/* We don't get a response on the sleep-confirmation */
priv->dnld_sent = DNLD_RES_RECEIVED;
if (priv->is_host_sleep_configured) {
priv->is_host_sleep_activated = 1;
wake_up_interruptible(&priv->host_sleep_q);
}
/* If nothing to do, go back to sleep (?) */
if (!kfifo_len(&priv->event_fifo) && !priv->resp_len[priv->resp_idx])
priv->psstate = PS_STATE_SLEEP;
spin_unlock_irqrestore(&priv->driver_lock, flags);
out:
lbs_deb_leave(LBS_DEB_HOST);
}
void lbs_ps_sleep(struct lbs_private *priv, int wait_option)
{
lbs_deb_enter(LBS_DEB_HOST);
/*
* PS is currently supported only in Infrastructure mode
* Remove this check if it is to be supported in IBSS mode also
*/
lbs_prepare_and_send_command(priv, CMD_802_11_PS_MODE,
CMD_SUBCMD_ENTER_PS, wait_option, 0, NULL);
lbs_deb_leave(LBS_DEB_HOST);
}
/**
* @brief This function sends Exit_PS command to firmware.
*
* @param priv A pointer to struct lbs_private structure
* @param wait_option wait response or not
* @return n/a
*/
void lbs_ps_wakeup(struct lbs_private *priv, int wait_option)
{
__le32 Localpsmode;
lbs_deb_enter(LBS_DEB_HOST);
Localpsmode = cpu_to_le32(LBS802_11POWERMODECAM);
lbs_prepare_and_send_command(priv, CMD_802_11_PS_MODE,
CMD_SUBCMD_EXIT_PS,
wait_option, 0, &Localpsmode);
lbs_deb_leave(LBS_DEB_HOST);
}
/**
* @brief This function checks condition and prepares to
* send sleep confirm command to firmware if ok.
*
* @param priv A pointer to struct lbs_private structure
* @param psmode Power Saving mode
* @return n/a
*/
void lbs_ps_confirm_sleep(struct lbs_private *priv)
{
unsigned long flags =0;
int allowed = 1;
lbs_deb_enter(LBS_DEB_HOST);
spin_lock_irqsave(&priv->driver_lock, flags);
if (priv->dnld_sent) {
allowed = 0;
lbs_deb_host("dnld_sent was set\n");
}
/* In-progress command? */
if (priv->cur_cmd) {
allowed = 0;
lbs_deb_host("cur_cmd was set\n");
}
/* Pending events or command responses? */
if (kfifo_len(&priv->event_fifo) || priv->resp_len[priv->resp_idx]) {
allowed = 0;
lbs_deb_host("pending events or command responses\n");
}
spin_unlock_irqrestore(&priv->driver_lock, flags);
if (allowed) {
lbs_deb_host("sending lbs_ps_confirm_sleep\n");
lbs_send_confirmsleep(priv);
} else {
lbs_deb_host("sleep confirm has been delayed\n");
}
lbs_deb_leave(LBS_DEB_HOST);
}
/**
* @brief Configures the transmission power control functionality.
*
* @param priv A pointer to struct lbs_private structure
* @param enable Transmission power control enable
* @param p0 Power level when link quality is good (dBm).
* @param p1 Power level when link quality is fair (dBm).
* @param p2 Power level when link quality is poor (dBm).
* @param usesnr Use Signal to Noise Ratio in TPC
*
* @return 0 on success
*/
int lbs_set_tpc_cfg(struct lbs_private *priv, int enable, int8_t p0, int8_t p1,
int8_t p2, int usesnr)
{
struct cmd_ds_802_11_tpc_cfg cmd;
int ret;
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_ACT_SET);
cmd.enable = !!enable;
cmd.usesnr = !!usesnr;
cmd.P0 = p0;
cmd.P1 = p1;
cmd.P2 = p2;
ret = lbs_cmd_with_response(priv, CMD_802_11_TPC_CFG, &cmd);
return ret;
}
/**
* @brief Configures the power adaptation settings.
*
* @param priv A pointer to struct lbs_private structure
* @param enable Power adaptation enable
* @param p0 Power level for 1, 2, 5.5 and 11 Mbps (dBm).
* @param p1 Power level for 6, 9, 12, 18, 22, 24 and 36 Mbps (dBm).
* @param p2 Power level for 48 and 54 Mbps (dBm).
*
* @return 0 on Success
*/
int lbs_set_power_adapt_cfg(struct lbs_private *priv, int enable, int8_t p0,
int8_t p1, int8_t p2)
{
struct cmd_ds_802_11_pa_cfg cmd;
int ret;
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.size = cpu_to_le16(sizeof(cmd));
cmd.action = cpu_to_le16(CMD_ACT_SET);
cmd.enable = !!enable;
cmd.P0 = p0;
cmd.P1 = p1;
cmd.P2 = p2;
ret = lbs_cmd_with_response(priv, CMD_802_11_PA_CFG , &cmd);
return ret;
}
struct cmd_ctrl_node *__lbs_cmd_async(struct lbs_private *priv,
uint16_t command, struct cmd_header *in_cmd, int in_cmd_size,
int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *),
unsigned long callback_arg)
{
struct cmd_ctrl_node *cmdnode;
lbs_deb_enter(LBS_DEB_HOST);
if (priv->surpriseremoved) {
lbs_deb_host("PREP_CMD: card removed\n");
cmdnode = ERR_PTR(-ENOENT);
goto done;
}
if (!lbs_is_cmd_allowed(priv)) {
cmdnode = ERR_PTR(-EBUSY);
goto done;
}
cmdnode = lbs_get_cmd_ctrl_node(priv);
if (cmdnode == NULL) {
lbs_deb_host("PREP_CMD: cmdnode is NULL\n");
/* Wake up main thread to execute next command */
wake_up_interruptible(&priv->waitq);
cmdnode = ERR_PTR(-ENOBUFS);
goto done;
}
cmdnode->callback = callback;
cmdnode->callback_arg = callback_arg;
/* Copy the incoming command to the buffer */
memcpy(cmdnode->cmdbuf, in_cmd, in_cmd_size);
/* Set sequence number, clean result, move to buffer */
priv->seqnum++;
cmdnode->cmdbuf->command = cpu_to_le16(command);
cmdnode->cmdbuf->size = cpu_to_le16(in_cmd_size);
cmdnode->cmdbuf->seqnum = cpu_to_le16(priv->seqnum);
cmdnode->cmdbuf->result = 0;
lbs_deb_host("PREP_CMD: command 0x%04x\n", command);
cmdnode->cmdwaitqwoken = 0;
lbs_queue_cmd(priv, cmdnode);
wake_up_interruptible(&priv->waitq);
done:
lbs_deb_leave_args(LBS_DEB_HOST, "ret %p", cmdnode);
return cmdnode;
}
void lbs_cmd_async(struct lbs_private *priv, uint16_t command,
struct cmd_header *in_cmd, int in_cmd_size)
{
lbs_deb_enter(LBS_DEB_CMD);
__lbs_cmd_async(priv, command, in_cmd, in_cmd_size,
lbs_cmd_async_callback, 0);
lbs_deb_leave(LBS_DEB_CMD);
}
int __lbs_cmd(struct lbs_private *priv, uint16_t command,
struct cmd_header *in_cmd, int in_cmd_size,
int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *),
unsigned long callback_arg)
{
struct cmd_ctrl_node *cmdnode;
unsigned long flags;
int ret = 0;
lbs_deb_enter(LBS_DEB_HOST);
cmdnode = __lbs_cmd_async(priv, command, in_cmd, in_cmd_size,
callback, callback_arg);
if (IS_ERR(cmdnode)) {
ret = PTR_ERR(cmdnode);
goto done;
}
might_sleep();
wait_event_interruptible(cmdnode->cmdwait_q, cmdnode->cmdwaitqwoken);
spin_lock_irqsave(&priv->driver_lock, flags);
ret = cmdnode->result;
if (ret)
lbs_pr_info("PREP_CMD: command 0x%04x failed: %d\n",
command, ret);
__lbs_cleanup_and_insert_cmd(priv, cmdnode);
spin_unlock_irqrestore(&priv->driver_lock, flags);
done:
lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret);
return ret;
}
EXPORT_SYMBOL_GPL(__lbs_cmd);