Inaky Perez-Gonzalez | 024f7f3 | 2008-12-20 16:57:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Intel Wireless WiMAX Connection 2400m |
| 3 | * Implement backend for the WiMAX stack rfkill support |
| 4 | * |
| 5 | * |
| 6 | * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com> |
| 7 | * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> |
| 8 | * |
| 9 | * This program is free software; you can redistribute it and/or |
| 10 | * modify it under the terms of the GNU General Public License version |
| 11 | * 2 as published by the Free Software Foundation. |
| 12 | * |
| 13 | * This program is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | * GNU General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU General Public License |
| 19 | * along with this program; if not, write to the Free Software |
| 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 21 | * 02110-1301, USA. |
| 22 | * |
| 23 | * |
| 24 | * The WiMAX kernel stack integrates into RF-Kill and keeps the |
| 25 | * switches's status. We just need to: |
| 26 | * |
| 27 | * - report changes in the HW RF Kill switch [with |
| 28 | * wimax_rfkill_{sw,hw}_report(), which happens when we detect those |
| 29 | * indications coming through hardware reports]. We also do it on |
| 30 | * initialization to let the stack know the intial HW state. |
| 31 | * |
| 32 | * - implement indications from the stack to change the SW RF Kill |
| 33 | * switch (coming from sysfs, the wimax stack or user space). |
| 34 | */ |
| 35 | #include "i2400m.h" |
| 36 | #include <linux/wimax/i2400m.h> |
Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 37 | #include <linux/slab.h> |
Inaky Perez-Gonzalez | 024f7f3 | 2008-12-20 16:57:44 -0800 | [diff] [blame] | 38 | |
| 39 | |
| 40 | |
| 41 | #define D_SUBMODULE rfkill |
| 42 | #include "debug-levels.h" |
| 43 | |
| 44 | /* |
| 45 | * Return true if the i2400m radio is in the requested wimax_rf_state state |
| 46 | * |
| 47 | */ |
| 48 | static |
| 49 | int i2400m_radio_is(struct i2400m *i2400m, enum wimax_rf_state state) |
| 50 | { |
| 51 | if (state == WIMAX_RF_OFF) |
| 52 | return i2400m->state == I2400M_SS_RF_OFF |
| 53 | || i2400m->state == I2400M_SS_RF_SHUTDOWN; |
| 54 | else if (state == WIMAX_RF_ON) |
| 55 | /* state == WIMAX_RF_ON */ |
| 56 | return i2400m->state != I2400M_SS_RF_OFF |
| 57 | && i2400m->state != I2400M_SS_RF_SHUTDOWN; |
Inaky Perez-Gonzalez | 98eb0f5 | 2009-06-11 11:13:41 -0700 | [diff] [blame] | 58 | else { |
Inaky Perez-Gonzalez | 024f7f3 | 2008-12-20 16:57:44 -0800 | [diff] [blame] | 59 | BUG(); |
Inaky Perez-Gonzalez | 98eb0f5 | 2009-06-11 11:13:41 -0700 | [diff] [blame] | 60 | return -EINVAL; /* shut gcc warnings on certain arches */ |
| 61 | } |
Inaky Perez-Gonzalez | 024f7f3 | 2008-12-20 16:57:44 -0800 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | |
| 65 | /* |
| 66 | * WiMAX stack operation: implement SW RFKill toggling |
| 67 | * |
| 68 | * @wimax_dev: device descriptor |
| 69 | * @skb: skb where the message has been received; skb->data is |
| 70 | * expected to point to the message payload. |
| 71 | * @genl_info: passed by the generic netlink layer |
| 72 | * |
| 73 | * Generic Netlink will call this function when a message is sent from |
| 74 | * userspace to change the software RF-Kill switch status. |
| 75 | * |
| 76 | * This function will set the device's sofware RF-Kill switch state to |
| 77 | * match what is requested. |
| 78 | * |
| 79 | * NOTE: the i2400m has a strict state machine; we can only set the |
| 80 | * RF-Kill switch when it is on, the HW RF-Kill is on and the |
| 81 | * device is initialized. So we ignore errors steaming from not |
| 82 | * being in the right state (-EILSEQ). |
| 83 | */ |
| 84 | int i2400m_op_rfkill_sw_toggle(struct wimax_dev *wimax_dev, |
| 85 | enum wimax_rf_state state) |
| 86 | { |
| 87 | int result; |
| 88 | struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev); |
| 89 | struct device *dev = i2400m_dev(i2400m); |
| 90 | struct sk_buff *ack_skb; |
| 91 | struct { |
| 92 | struct i2400m_l3l4_hdr hdr; |
| 93 | struct i2400m_tlv_rf_operation sw_rf; |
| 94 | } __attribute__((packed)) *cmd; |
| 95 | char strerr[32]; |
| 96 | |
| 97 | d_fnstart(4, dev, "(wimax_dev %p state %d)\n", wimax_dev, state); |
| 98 | |
| 99 | result = -ENOMEM; |
| 100 | cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); |
| 101 | if (cmd == NULL) |
| 102 | goto error_alloc; |
| 103 | cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_RF_CONTROL); |
| 104 | cmd->hdr.length = sizeof(cmd->sw_rf); |
| 105 | cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION); |
| 106 | cmd->sw_rf.hdr.type = cpu_to_le16(I2400M_TLV_RF_OPERATION); |
| 107 | cmd->sw_rf.hdr.length = cpu_to_le16(sizeof(cmd->sw_rf.status)); |
| 108 | switch (state) { |
| 109 | case WIMAX_RF_OFF: /* RFKILL ON, radio OFF */ |
| 110 | cmd->sw_rf.status = cpu_to_le32(2); |
| 111 | break; |
| 112 | case WIMAX_RF_ON: /* RFKILL OFF, radio ON */ |
| 113 | cmd->sw_rf.status = cpu_to_le32(1); |
| 114 | break; |
| 115 | default: |
| 116 | BUG(); |
| 117 | } |
| 118 | |
| 119 | ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); |
| 120 | result = PTR_ERR(ack_skb); |
| 121 | if (IS_ERR(ack_skb)) { |
| 122 | dev_err(dev, "Failed to issue 'RF Control' command: %d\n", |
| 123 | result); |
| 124 | goto error_msg_to_dev; |
| 125 | } |
| 126 | result = i2400m_msg_check_status(wimax_msg_data(ack_skb), |
| 127 | strerr, sizeof(strerr)); |
| 128 | if (result < 0) { |
| 129 | dev_err(dev, "'RF Control' (0x%04x) command failed: %d - %s\n", |
| 130 | I2400M_MT_CMD_RF_CONTROL, result, strerr); |
| 131 | goto error_cmd; |
| 132 | } |
| 133 | |
| 134 | /* Now we wait for the state to change to RADIO_OFF or RADIO_ON */ |
| 135 | result = wait_event_timeout( |
| 136 | i2400m->state_wq, i2400m_radio_is(i2400m, state), |
| 137 | 5 * HZ); |
| 138 | if (result == 0) |
| 139 | result = -ETIMEDOUT; |
| 140 | if (result < 0) |
| 141 | dev_err(dev, "Error waiting for device to toggle RF state: " |
| 142 | "%d\n", result); |
| 143 | result = 0; |
| 144 | error_cmd: |
| 145 | kfree_skb(ack_skb); |
| 146 | error_msg_to_dev: |
| 147 | error_alloc: |
| 148 | d_fnend(4, dev, "(wimax_dev %p state %d) = %d\n", |
| 149 | wimax_dev, state, result); |
| 150 | return result; |
| 151 | } |
| 152 | |
| 153 | |
| 154 | /* |
| 155 | * Inform the WiMAX stack of changes in the RF Kill switches reported |
| 156 | * by the device |
| 157 | * |
| 158 | * @i2400m: device descriptor |
| 159 | * @rfss: TLV for RF Switches status; already validated |
| 160 | * |
| 161 | * NOTE: the reports on RF switch status cannot be trusted |
| 162 | * or used until the device is in a state of RADIO_OFF |
| 163 | * or greater. |
| 164 | */ |
| 165 | void i2400m_report_tlv_rf_switches_status( |
| 166 | struct i2400m *i2400m, |
| 167 | const struct i2400m_tlv_rf_switches_status *rfss) |
| 168 | { |
| 169 | struct device *dev = i2400m_dev(i2400m); |
| 170 | enum i2400m_rf_switch_status hw, sw; |
| 171 | enum wimax_st wimax_state; |
| 172 | |
| 173 | sw = le32_to_cpu(rfss->sw_rf_switch); |
| 174 | hw = le32_to_cpu(rfss->hw_rf_switch); |
| 175 | |
| 176 | d_fnstart(3, dev, "(i2400m %p rfss %p [hw %u sw %u])\n", |
| 177 | i2400m, rfss, hw, sw); |
| 178 | /* We only process rw switch evens when the device has been |
| 179 | * fully initialized */ |
| 180 | wimax_state = wimax_state_get(&i2400m->wimax_dev); |
| 181 | if (wimax_state < WIMAX_ST_RADIO_OFF) { |
| 182 | d_printf(3, dev, "ignoring RF switches report, state %u\n", |
| 183 | wimax_state); |
| 184 | goto out; |
| 185 | } |
| 186 | switch (sw) { |
| 187 | case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */ |
| 188 | wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_ON); |
| 189 | break; |
| 190 | case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */ |
| 191 | wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_OFF); |
| 192 | break; |
| 193 | default: |
| 194 | dev_err(dev, "HW BUG? Unknown RF SW state 0x%x\n", sw); |
| 195 | } |
| 196 | |
| 197 | switch (hw) { |
| 198 | case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */ |
| 199 | wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_ON); |
| 200 | break; |
| 201 | case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */ |
| 202 | wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_OFF); |
| 203 | break; |
| 204 | default: |
| 205 | dev_err(dev, "HW BUG? Unknown RF HW state 0x%x\n", hw); |
| 206 | } |
| 207 | out: |
| 208 | d_fnend(3, dev, "(i2400m %p rfss %p [hw %u sw %u]) = void\n", |
| 209 | i2400m, rfss, hw, sw); |
| 210 | } |