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