// SPDX-License-Identifier: GPL-2.0-only /* * This file contains the handling of command. * It prepares command and sends it to firmware when it is ready.
*/
/** * lbs_cmd_copyback - Simple callback that copies response back into command * * @priv: A pointer to &struct lbs_private structure * @extra: A pointer to the original command structure for which * 'resp' is a response * @resp: A pointer to the command response * * returns: 0 on success, error on failure
*/ int lbs_cmd_copyback(struct lbs_private *priv, unsignedlong extra, struct cmd_header *resp)
{ struct cmd_header *buf = (void *)extra;
uint16_t copy_len;
/** * lbs_cmd_async_callback - 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. * * @priv: ignored * @extra: ignored * @resp: ignored * * returns: 0 for success
*/ staticint lbs_cmd_async_callback(struct lbs_private *priv, unsignedlong extra, struct cmd_header *resp)
{ return 0;
}
/** * is_command_allowed_in_ps - tests if a command is allowed in Power Save mode * * @cmd: the command ID * * returns: 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;
}
/** * lbs_update_hw_spec - Updates the hardware details like MAC address * and regulatory region * * @priv: A pointer to &struct lbs_private structure * * returns: 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;
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);
/* 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;
netdev_info(priv->dev, "unidentified region code; using the default (USA)\n");
}
if (priv->current_addr[0] == 0xff)
memmove(priv->current_addr, cmd.permanentaddr, ETH_ALEN);
if (!priv->copied_hwaddr) {
eth_hw_addr_set(priv->dev, priv->current_addr); if (priv->mesh_dev)
eth_hw_addr_set(priv->mesh_dev, priv->current_addr);
priv->copied_hwaddr = 1;
}
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;
/* * Certain firmware versions do not support EHS_REMOVE_WAKEUP command * and the card will return a failure. Since we need to be * able to reset the mask, in those cases we set a 0 mask instead.
*/ if (criteria == EHS_REMOVE_WAKEUP && !priv->ehs_remove_supported)
criteria = 0;
/** * lbs_set_ps_mode - Sets the Power Save mode * * @priv: A pointer to &struct lbs_private structure * @cmd_action: The Power Save operation (PS_MODE_ACTION_ENTER_PS or * PS_MODE_ACTION_EXIT_PS) * @block: Whether to block on a response or not * * returns: 0 on success, error on failure
*/ int lbs_set_ps_mode(struct lbs_private *priv, u16 cmd_action, bool block)
{ struct cmd_ds_802_11_ps_mode cmd; int ret = 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;
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) {
netdev_info(priv->dev, "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)
netdev_info(priv->dev, "HOST_SLEEP_ACTIVATE failed: %d\n",
ret);
}
if (!wait_event_interruptible_timeout(
priv->host_sleep_q,
priv->is_host_sleep_activated,
(10 * HZ))) {
netdev_err(priv->dev, "host_sleep_q: timer expired\n");
ret = -1;
}
} else {
netdev_err(priv->dev, "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;
}
/** * lbs_set_snmp_mib - Set an SNMP MIB value * * @priv: A pointer to &struct lbs_private structure * @oid: The OID to set in the firmware * @val: Value to set the OID to * * returns: 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;
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: return ret;
}
/** * lbs_get_tx_power - Get the min, max, and current TX power * * @priv: A pointer to &struct lbs_private structure * @curlevel: Current power level in dBm * @minlevel: Minimum supported power level in dBm (optional) * @maxlevel: Maximum supported power level in dBm (optional) * * returns: 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;
ret = lbs_cmd_with_response(priv, CMD_802_11_MONITOR_MODE, &cmd); if (ret == 0) {
priv->dev->type = enable ? ARPHRD_IEEE80211_RADIOTAP :
ARPHRD_ETHER;
}
return ret;
}
/** * lbs_get_channel - Get the radio channel * * @priv: A pointer to &struct lbs_private structure * * returns: The channel on success, error on failure
*/ staticint lbs_get_channel(struct lbs_private *priv)
{ struct cmd_ds_802_11_rf_channel cmd; int ret = 0;
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: 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 */
ret = lbs_get_channel(priv); if (ret > 0) {
priv->channel = ret;
ret = 0;
}
return ret;
}
/** * lbs_set_channel - Set the radio channel * * @priv: A pointer to &struct lbs_private structure * @channel: The desired channel, or 0 to clear a locked channel * * returns: 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;
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: return ret;
}
/** * lbs_get_rssi - Get current RSSI and noise floor * * @priv: A pointer to &struct lbs_private structure * @rssi: On successful return, signal level in mBm * @nf: On successful return, Noise floor * * returns: 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;
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));
}
return ret;
}
/** * lbs_set_11d_domain_info - Send regulatory and 802.11d domain information * to the firmware * * @priv: pointer to &struct lbs_private * * returns: 0 on success, error code on failure
*/ int lbs_set_11d_domain_info(struct lbs_private *priv)
{ struct wiphy *wiphy = priv->wdev->wiphy; struct ieee80211_supported_band **bands = wiphy->bands; struct cmd_ds_802_11d_domain_info cmd; struct mrvl_ie_domain_param_set *domain = &cmd.domain; struct ieee80211_country_ie_triplet *t; enum nl80211_band band; struct ieee80211_channel *ch;
u8 num_triplet = 0;
u8 num_parsed_chan = 0;
u8 first_channel = 0, next_chan = 0, max_pwr = 0;
u8 i, flag = 0;
size_t triplet_size; int ret = 0;
/* Set country code */
domain->country_code[0] = priv->country_code[0];
domain->country_code[1] = priv->country_code[1];
domain->country_code[2] = ' ';
/* Now set up the channel triplets; firmware is somewhat picky here * and doesn't validate channel numbers and spans; hence it would * interpret a triplet of (36, 4, 20) as channels 36, 37, 38, 39. Since * the last 3 aren't valid channels, the driver is responsible for * splitting that up into 4 triplet pairs of (36, 1, 20) + (40, 1, 20) * etc.
*/ for (band = 0;
(band < NUM_NL80211_BANDS) && (num_triplet < MAX_11D_TRIPLETS);
band++) {
if (!bands[band]) continue;
for (i = 0;
(i < bands[band]->n_channels) && (num_triplet < MAX_11D_TRIPLETS);
i++) {
ch = &bands[band]->channels[i]; if (ch->flags & IEEE80211_CHAN_DISABLED) continue;
if (!flag) {
flag = 1;
next_chan = first_channel = (u32) ch->hw_value;
max_pwr = ch->max_power;
num_parsed_chan = 1; continue;
}
ret = lbs_cmd_with_response(priv, CMD_802_11D_DOMAIN_INFO, &cmd);
out: return ret;
}
/** * lbs_get_reg - Read a MAC, Baseband, or RF register * * @priv: pointer to &struct lbs_private * @reg: register command, one of CMD_MAC_REG_ACCESS, * CMD_BBP_REG_ACCESS, or CMD_RF_REG_ACCESS * @offset: byte offset of the register to get * @value: on success, the value of the register at 'offset' * * returns: 0 on success, error code on failure
*/ int lbs_get_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 *value)
{ struct cmd_ds_reg_access cmd; int ret = 0;
if (reg != CMD_MAC_REG_ACCESS &&
reg != CMD_BBP_REG_ACCESS &&
reg != CMD_RF_REG_ACCESS) {
ret = -EINVAL; goto out;
}
ret = lbs_cmd_with_response(priv, reg, &cmd); if (!ret) { if (reg == CMD_BBP_REG_ACCESS || reg == CMD_RF_REG_ACCESS)
*value = cmd.value.bbp_rf; elseif (reg == CMD_MAC_REG_ACCESS)
*value = le32_to_cpu(cmd.value.mac);
}
out: return ret;
}
/** * lbs_set_reg - Write a MAC, Baseband, or RF register * * @priv: pointer to &struct lbs_private * @reg: register command, one of CMD_MAC_REG_ACCESS, * CMD_BBP_REG_ACCESS, or CMD_RF_REG_ACCESS * @offset: byte offset of the register to set * @value: the value to write to the register at 'offset' * * returns: 0 on success, error code on failure
*/ int lbs_set_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 value)
{ struct cmd_ds_reg_access cmd; int ret = 0;
if (!cmdnode) {
lbs_deb_host("QUEUE_CMD: cmdnode is NULL\n"); return;
} if (!cmdnode->cmdbuf->size) {
lbs_deb_host("DNLD_CMD: cmd size is zero\n"); return;
}
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;
if (psm->action == cpu_to_le16(PS_MODE_ACTION_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);
if (command == CMD_802_11_DEEP_SLEEP) {
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);
}
}
/* * This function inserts command node to cmdfreeq * after cleans it. Requires priv->driver_lock held.
*/ staticvoid __lbs_cleanup_and_insert_cmd(struct lbs_private *priv, struct cmd_ctrl_node *cmdnode)
{ if (!cmdnode) return;
void __lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd, int result)
{ /* * Normally, commands are removed from cmdpendingq before being * submitted. However, we can arrive here on alternative codepaths * where the command is still pending. Make sure the command really * isn't part of a list at this point.
*/
list_del_init(&cmd->list);
/* 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;
}
}
/** * lbs_allocate_cmd_buffer - allocates the command buffer and links * it to command free queue * * @priv: A pointer to &struct lbs_private structure * * returns: 0 for success or -1 on error
*/ int lbs_allocate_cmd_buffer(struct lbs_private *priv)
{ int ret = 0;
u32 bufsize;
u32 i; struct cmd_ctrl_node *cmdarray;
/* 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 free_cmd_array;
}
}
for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) {
init_waitqueue_head(&cmdarray[i].cmdwait_q);
lbs_cleanup_and_insert_cmd(priv, &cmdarray[i]);
} return 0;
free_cmd_array: for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { if (cmdarray[i].cmdbuf) {
kfree(cmdarray[i].cmdbuf);
cmdarray[i].cmdbuf = NULL;
}
}
kfree(priv->cmd_array);
priv->cmd_array = NULL;
done: return ret;
}
/** * lbs_free_cmd_buffer - free the command buffer * * @priv: A pointer to &struct lbs_private structure * * returns: 0 for success
*/ int lbs_free_cmd_buffer(struct lbs_private *priv)
{ struct cmd_ctrl_node *cmdarray; unsignedint i;
/* 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;
}
}
/** * lbs_get_free_cmd_node - gets a free command node if available in * command free queue * * @priv: A pointer to &struct lbs_private structure * * returns: A pointer to &cmd_ctrl_node structure on success * or %NULL on error
*/ staticstruct cmd_ctrl_node *lbs_get_free_cmd_node(struct lbs_private *priv)
{ struct cmd_ctrl_node *tempnode; unsignedlong flags;
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_init(&tempnode->list);
} else {
lbs_deb_host("GET_CMD_NODE: cmd_ctrl_node is not available\n");
tempnode = NULL;
}
/** * lbs_execute_next_command - execute next command in command * pending queue. Will put firmware back to PS mode if applicable. * * @priv: A pointer to &struct lbs_private structure * * returns: 0 on success or -1 on error
*/ int lbs_execute_next_command(struct lbs_private *priv)
{ struct cmd_ctrl_node *cmdnode = NULL; struct cmd_header *cmd; unsignedlong 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 */
spin_lock_irqsave(&priv->driver_lock, flags);
if (priv->cur_cmd) {
netdev_alert(priv->dev, "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);
}
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);
} elseif (priv->psstate != PS_STATE_FULL_POWER) { /* * 1. Non-PS command: * Queue it. set needtowakeup to TRUE if current state * is SLEEP, otherwise call 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_set_ps_mode(priv,
PS_MODE_ACTION_EXIT_PS, false);
}
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;
/* No commands are allowed in Deep Sleep until we toggle the GPIO * to wake up the card and it has signaled that it's ready.
*/ if (priv->is_deep_sleep) {
lbs_deb_cmd("command not allowed in deep sleep\n");
cmdnode = ERR_PTR(-EBUSY); goto done;
}
cmdnode = lbs_get_free_cmd_node(priv); if (cmdnode == NULL) {
lbs_deb_host("PREP_CMD: cmdnode is NULL\n");
/* Wake up main thread to execute next command */
wake_up(&priv->waitq);
cmdnode = ERR_PTR(-ENOBUFS); goto done;
}
int __lbs_cmd(struct lbs_private *priv, uint16_t command, struct cmd_header *in_cmd, int in_cmd_size, int (*callback)(struct lbs_private *, unsignedlong, struct cmd_header *), unsignedlong callback_arg)
{ struct cmd_ctrl_node *cmdnode; unsignedlong flags; int ret = 0;
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();
/* * Be careful with signals here. A signal may be received as the system * goes into suspend or resume. We do not want this to interrupt the * command, so we perform an uninterruptible sleep.
*/
wait_event(cmdnode->cmdwait_q, cmdnode->cmdwaitqwoken);
spin_lock_irqsave(&priv->driver_lock, flags);
ret = cmdnode->result; if (ret)
netdev_info(priv->dev, "PREP_CMD: command 0x%04x failed: %d\n",
command, ret);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.