| /* | |
| * Intel Wireless WiMAX Connection 2400m | |
| * Implement backend for the WiMAX stack rfkill support | |
| * | |
| * | |
| * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com> | |
| * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> | |
| * | |
| * This program is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public License version | |
| * 2 as published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| * 02110-1301, USA. | |
| * | |
| * | |
| * 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 = 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); | |
| 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); | |
| } |