mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
5.8 KiB
196 lines
5.8 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Intel Wireless WiMAX Connection 2400m |
|
* Implement backend for the WiMAX stack rfkill support |
|
* |
|
* Copyright (C) 2007-2008 Intel Corporation <[email protected]> |
|
* Inaky Perez-Gonzalez <[email protected]> |
|
* |
|
* The WiMAX kernel stack integrates into RF-Kill and keeps the |
|
* switches's status. We just need to: |
|
* |
|
* - report changes in the HW RF Kill switch [with |
|
* wimax_rfkill_{sw,hw}_report(), which happens when we detect those |
|
* indications coming through hardware reports]. We also do it on |
|
* initialization to let the stack know the initial HW state. |
|
* |
|
* - implement indications from the stack to change the SW RF Kill |
|
* switch (coming from sysfs, the wimax stack or user space). |
|
*/ |
|
#include "i2400m.h" |
|
#include <linux/wimax/i2400m.h> |
|
#include <linux/slab.h> |
|
|
|
|
|
|
|
#define D_SUBMODULE rfkill |
|
#include "debug-levels.h" |
|
|
|
/* |
|
* Return true if the i2400m radio is in the requested wimax_rf_state state |
|
* |
|
*/ |
|
static |
|
int i2400m_radio_is(struct i2400m *i2400m, enum wimax_rf_state state) |
|
{ |
|
if (state == WIMAX_RF_OFF) |
|
return i2400m->state == I2400M_SS_RF_OFF |
|
|| i2400m->state == I2400M_SS_RF_SHUTDOWN; |
|
else if (state == WIMAX_RF_ON) |
|
/* state == WIMAX_RF_ON */ |
|
return i2400m->state != I2400M_SS_RF_OFF |
|
&& i2400m->state != I2400M_SS_RF_SHUTDOWN; |
|
else { |
|
BUG(); |
|
return -EINVAL; /* shut gcc warnings on certain arches */ |
|
} |
|
} |
|
|
|
|
|
/* |
|
* WiMAX stack operation: implement SW RFKill toggling |
|
* |
|
* @wimax_dev: device descriptor |
|
* @skb: skb where the message has been received; skb->data is |
|
* expected to point to the message payload. |
|
* @genl_info: passed by the generic netlink layer |
|
* |
|
* Generic Netlink will call this function when a message is sent from |
|
* userspace to change the software RF-Kill switch status. |
|
* |
|
* This function will set the device's software RF-Kill switch state to |
|
* match what is requested. |
|
* |
|
* NOTE: the i2400m has a strict state machine; we can only set the |
|
* RF-Kill switch when it is on, the HW RF-Kill is on and the |
|
* device is initialized. So we ignore errors steaming from not |
|
* being in the right state (-EILSEQ). |
|
*/ |
|
int i2400m_op_rfkill_sw_toggle(struct wimax_dev *wimax_dev, |
|
enum wimax_rf_state state) |
|
{ |
|
int result; |
|
struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev); |
|
struct device *dev = i2400m_dev(i2400m); |
|
struct sk_buff *ack_skb; |
|
struct { |
|
struct i2400m_l3l4_hdr hdr; |
|
struct i2400m_tlv_rf_operation sw_rf; |
|
} __packed *cmd; |
|
char strerr[32]; |
|
|
|
d_fnstart(4, dev, "(wimax_dev %p state %d)\n", wimax_dev, state); |
|
|
|
result = -ENOMEM; |
|
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); |
|
if (cmd == NULL) |
|
goto error_alloc; |
|
cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_RF_CONTROL); |
|
cmd->hdr.length = cpu_to_le16(sizeof(cmd->sw_rf)); |
|
cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION); |
|
cmd->sw_rf.hdr.type = cpu_to_le16(I2400M_TLV_RF_OPERATION); |
|
cmd->sw_rf.hdr.length = cpu_to_le16(sizeof(cmd->sw_rf.status)); |
|
switch (state) { |
|
case WIMAX_RF_OFF: /* RFKILL ON, radio OFF */ |
|
cmd->sw_rf.status = cpu_to_le32(2); |
|
break; |
|
case WIMAX_RF_ON: /* RFKILL OFF, radio ON */ |
|
cmd->sw_rf.status = cpu_to_le32(1); |
|
break; |
|
default: |
|
BUG(); |
|
} |
|
|
|
ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); |
|
result = PTR_ERR(ack_skb); |
|
if (IS_ERR(ack_skb)) { |
|
dev_err(dev, "Failed to issue 'RF Control' command: %d\n", |
|
result); |
|
goto error_msg_to_dev; |
|
} |
|
result = i2400m_msg_check_status(wimax_msg_data(ack_skb), |
|
strerr, sizeof(strerr)); |
|
if (result < 0) { |
|
dev_err(dev, "'RF Control' (0x%04x) command failed: %d - %s\n", |
|
I2400M_MT_CMD_RF_CONTROL, result, strerr); |
|
goto error_cmd; |
|
} |
|
|
|
/* Now we wait for the state to change to RADIO_OFF or RADIO_ON */ |
|
result = wait_event_timeout( |
|
i2400m->state_wq, i2400m_radio_is(i2400m, state), |
|
5 * HZ); |
|
if (result == 0) |
|
result = -ETIMEDOUT; |
|
if (result < 0) |
|
dev_err(dev, "Error waiting for device to toggle RF state: " |
|
"%d\n", result); |
|
result = 0; |
|
error_cmd: |
|
kfree_skb(ack_skb); |
|
error_msg_to_dev: |
|
error_alloc: |
|
d_fnend(4, dev, "(wimax_dev %p state %d) = %d\n", |
|
wimax_dev, state, result); |
|
kfree(cmd); |
|
return result; |
|
} |
|
|
|
|
|
/* |
|
* Inform the WiMAX stack of changes in the RF Kill switches reported |
|
* by the device |
|
* |
|
* @i2400m: device descriptor |
|
* @rfss: TLV for RF Switches status; already validated |
|
* |
|
* NOTE: the reports on RF switch status cannot be trusted |
|
* or used until the device is in a state of RADIO_OFF |
|
* or greater. |
|
*/ |
|
void i2400m_report_tlv_rf_switches_status( |
|
struct i2400m *i2400m, |
|
const struct i2400m_tlv_rf_switches_status *rfss) |
|
{ |
|
struct device *dev = i2400m_dev(i2400m); |
|
enum i2400m_rf_switch_status hw, sw; |
|
enum wimax_st wimax_state; |
|
|
|
sw = le32_to_cpu(rfss->sw_rf_switch); |
|
hw = le32_to_cpu(rfss->hw_rf_switch); |
|
|
|
d_fnstart(3, dev, "(i2400m %p rfss %p [hw %u sw %u])\n", |
|
i2400m, rfss, hw, sw); |
|
/* We only process rw switch evens when the device has been |
|
* fully initialized */ |
|
wimax_state = wimax_state_get(&i2400m->wimax_dev); |
|
if (wimax_state < WIMAX_ST_RADIO_OFF) { |
|
d_printf(3, dev, "ignoring RF switches report, state %u\n", |
|
wimax_state); |
|
goto out; |
|
} |
|
switch (sw) { |
|
case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */ |
|
wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_ON); |
|
break; |
|
case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */ |
|
wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_OFF); |
|
break; |
|
default: |
|
dev_err(dev, "HW BUG? Unknown RF SW state 0x%x\n", sw); |
|
} |
|
|
|
switch (hw) { |
|
case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */ |
|
wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_ON); |
|
break; |
|
case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */ |
|
wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_OFF); |
|
break; |
|
default: |
|
dev_err(dev, "HW BUG? Unknown RF HW state 0x%x\n", hw); |
|
} |
|
out: |
|
d_fnend(3, dev, "(i2400m %p rfss %p [hw %u sw %u]) = void\n", |
|
i2400m, rfss, hw, sw); |
|
}
|
|
|