Commit | Line | Data |
---|---|---|
024f7f31 IPG |
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> | |
37 | ||
38 | ||
39 | ||
40 | #define D_SUBMODULE rfkill | |
41 | #include "debug-levels.h" | |
42 | ||
43 | /* | |
44 | * Return true if the i2400m radio is in the requested wimax_rf_state state | |
45 | * | |
46 | */ | |
47 | static | |
48 | int i2400m_radio_is(struct i2400m *i2400m, enum wimax_rf_state state) | |
49 | { | |
50 | if (state == WIMAX_RF_OFF) | |
51 | return i2400m->state == I2400M_SS_RF_OFF | |
52 | || i2400m->state == I2400M_SS_RF_SHUTDOWN; | |
53 | else if (state == WIMAX_RF_ON) | |
54 | /* state == WIMAX_RF_ON */ | |
55 | return i2400m->state != I2400M_SS_RF_OFF | |
56 | && i2400m->state != I2400M_SS_RF_SHUTDOWN; | |
98eb0f53 | 57 | else { |
024f7f31 | 58 | BUG(); |
98eb0f53 IPG |
59 | return -EINVAL; /* shut gcc warnings on certain arches */ |
60 | } | |
024f7f31 IPG |
61 | } |
62 | ||
63 | ||
64 | /* | |
65 | * WiMAX stack operation: implement SW RFKill toggling | |
66 | * | |
67 | * @wimax_dev: device descriptor | |
68 | * @skb: skb where the message has been received; skb->data is | |
69 | * expected to point to the message payload. | |
70 | * @genl_info: passed by the generic netlink layer | |
71 | * | |
72 | * Generic Netlink will call this function when a message is sent from | |
73 | * userspace to change the software RF-Kill switch status. | |
74 | * | |
75 | * This function will set the device's sofware RF-Kill switch state to | |
76 | * match what is requested. | |
77 | * | |
78 | * NOTE: the i2400m has a strict state machine; we can only set the | |
79 | * RF-Kill switch when it is on, the HW RF-Kill is on and the | |
80 | * device is initialized. So we ignore errors steaming from not | |
81 | * being in the right state (-EILSEQ). | |
82 | */ | |
83 | int i2400m_op_rfkill_sw_toggle(struct wimax_dev *wimax_dev, | |
84 | enum wimax_rf_state state) | |
85 | { | |
86 | int result; | |
87 | struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev); | |
88 | struct device *dev = i2400m_dev(i2400m); | |
89 | struct sk_buff *ack_skb; | |
90 | struct { | |
91 | struct i2400m_l3l4_hdr hdr; | |
92 | struct i2400m_tlv_rf_operation sw_rf; | |
93 | } __attribute__((packed)) *cmd; | |
94 | char strerr[32]; | |
95 | ||
96 | d_fnstart(4, dev, "(wimax_dev %p state %d)\n", wimax_dev, state); | |
97 | ||
98 | result = -ENOMEM; | |
99 | cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); | |
100 | if (cmd == NULL) | |
101 | goto error_alloc; | |
102 | cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_RF_CONTROL); | |
103 | cmd->hdr.length = sizeof(cmd->sw_rf); | |
104 | cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION); | |
105 | cmd->sw_rf.hdr.type = cpu_to_le16(I2400M_TLV_RF_OPERATION); | |
106 | cmd->sw_rf.hdr.length = cpu_to_le16(sizeof(cmd->sw_rf.status)); | |
107 | switch (state) { | |
108 | case WIMAX_RF_OFF: /* RFKILL ON, radio OFF */ | |
109 | cmd->sw_rf.status = cpu_to_le32(2); | |
110 | break; | |
111 | case WIMAX_RF_ON: /* RFKILL OFF, radio ON */ | |
112 | cmd->sw_rf.status = cpu_to_le32(1); | |
113 | break; | |
114 | default: | |
115 | BUG(); | |
116 | } | |
117 | ||
118 | ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); | |
119 | result = PTR_ERR(ack_skb); | |
120 | if (IS_ERR(ack_skb)) { | |
121 | dev_err(dev, "Failed to issue 'RF Control' command: %d\n", | |
122 | result); | |
123 | goto error_msg_to_dev; | |
124 | } | |
125 | result = i2400m_msg_check_status(wimax_msg_data(ack_skb), | |
126 | strerr, sizeof(strerr)); | |
127 | if (result < 0) { | |
128 | dev_err(dev, "'RF Control' (0x%04x) command failed: %d - %s\n", | |
129 | I2400M_MT_CMD_RF_CONTROL, result, strerr); | |
130 | goto error_cmd; | |
131 | } | |
132 | ||
133 | /* Now we wait for the state to change to RADIO_OFF or RADIO_ON */ | |
134 | result = wait_event_timeout( | |
135 | i2400m->state_wq, i2400m_radio_is(i2400m, state), | |
136 | 5 * HZ); | |
137 | if (result == 0) | |
138 | result = -ETIMEDOUT; | |
139 | if (result < 0) | |
140 | dev_err(dev, "Error waiting for device to toggle RF state: " | |
141 | "%d\n", result); | |
142 | result = 0; | |
143 | error_cmd: | |
144 | kfree_skb(ack_skb); | |
145 | error_msg_to_dev: | |
146 | error_alloc: | |
147 | d_fnend(4, dev, "(wimax_dev %p state %d) = %d\n", | |
148 | wimax_dev, state, result); | |
149 | return result; | |
150 | } | |
151 | ||
152 | ||
153 | /* | |
154 | * Inform the WiMAX stack of changes in the RF Kill switches reported | |
155 | * by the device | |
156 | * | |
157 | * @i2400m: device descriptor | |
158 | * @rfss: TLV for RF Switches status; already validated | |
159 | * | |
160 | * NOTE: the reports on RF switch status cannot be trusted | |
161 | * or used until the device is in a state of RADIO_OFF | |
162 | * or greater. | |
163 | */ | |
164 | void i2400m_report_tlv_rf_switches_status( | |
165 | struct i2400m *i2400m, | |
166 | const struct i2400m_tlv_rf_switches_status *rfss) | |
167 | { | |
168 | struct device *dev = i2400m_dev(i2400m); | |
169 | enum i2400m_rf_switch_status hw, sw; | |
170 | enum wimax_st wimax_state; | |
171 | ||
172 | sw = le32_to_cpu(rfss->sw_rf_switch); | |
173 | hw = le32_to_cpu(rfss->hw_rf_switch); | |
174 | ||
175 | d_fnstart(3, dev, "(i2400m %p rfss %p [hw %u sw %u])\n", | |
176 | i2400m, rfss, hw, sw); | |
177 | /* We only process rw switch evens when the device has been | |
178 | * fully initialized */ | |
179 | wimax_state = wimax_state_get(&i2400m->wimax_dev); | |
180 | if (wimax_state < WIMAX_ST_RADIO_OFF) { | |
181 | d_printf(3, dev, "ignoring RF switches report, state %u\n", | |
182 | wimax_state); | |
183 | goto out; | |
184 | } | |
185 | switch (sw) { | |
186 | case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */ | |
187 | wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_ON); | |
188 | break; | |
189 | case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */ | |
190 | wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_OFF); | |
191 | break; | |
192 | default: | |
193 | dev_err(dev, "HW BUG? Unknown RF SW state 0x%x\n", sw); | |
194 | } | |
195 | ||
196 | switch (hw) { | |
197 | case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */ | |
198 | wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_ON); | |
199 | break; | |
200 | case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */ | |
201 | wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_OFF); | |
202 | break; | |
203 | default: | |
204 | dev_err(dev, "HW BUG? Unknown RF HW state 0x%x\n", hw); | |
205 | } | |
206 | out: | |
207 | d_fnend(3, dev, "(i2400m %p rfss %p [hw %u sw %u]) = void\n", | |
208 | i2400m, rfss, hw, sw); | |
209 | } |