diff options
Diffstat (limited to 'drivers/net/wireless/wl12xx/testmode.c')
-rw-r--r-- | drivers/net/wireless/wl12xx/testmode.c | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/drivers/net/wireless/wl12xx/testmode.c b/drivers/net/wireless/wl12xx/testmode.c new file mode 100644 index 000000000000..e64403b6896d --- /dev/null +++ b/drivers/net/wireless/wl12xx/testmode.c | |||
@@ -0,0 +1,290 @@ | |||
1 | /* | ||
2 | * This file is part of wl1271 | ||
3 | * | ||
4 | * Copyright (C) 2010 Nokia Corporation | ||
5 | * | ||
6 | * Contact: Luciano Coelho <luciano.coelho@nokia.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License | ||
10 | * version 2 as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
20 | * 02110-1301 USA | ||
21 | * | ||
22 | */ | ||
23 | #include "testmode.h" | ||
24 | |||
25 | #include <linux/slab.h> | ||
26 | #include <net/genetlink.h> | ||
27 | |||
28 | #include "wl12xx.h" | ||
29 | #include "acx.h" | ||
30 | |||
31 | #define WL1271_TM_MAX_DATA_LENGTH 1024 | ||
32 | |||
33 | enum wl1271_tm_commands { | ||
34 | WL1271_TM_CMD_UNSPEC, | ||
35 | WL1271_TM_CMD_TEST, | ||
36 | WL1271_TM_CMD_INTERROGATE, | ||
37 | WL1271_TM_CMD_CONFIGURE, | ||
38 | WL1271_TM_CMD_NVS_PUSH, | ||
39 | WL1271_TM_CMD_SET_PLT_MODE, | ||
40 | WL1271_TM_CMD_RECOVER, | ||
41 | |||
42 | __WL1271_TM_CMD_AFTER_LAST | ||
43 | }; | ||
44 | #define WL1271_TM_CMD_MAX (__WL1271_TM_CMD_AFTER_LAST - 1) | ||
45 | |||
46 | enum wl1271_tm_attrs { | ||
47 | WL1271_TM_ATTR_UNSPEC, | ||
48 | WL1271_TM_ATTR_CMD_ID, | ||
49 | WL1271_TM_ATTR_ANSWER, | ||
50 | WL1271_TM_ATTR_DATA, | ||
51 | WL1271_TM_ATTR_IE_ID, | ||
52 | WL1271_TM_ATTR_PLT_MODE, | ||
53 | |||
54 | __WL1271_TM_ATTR_AFTER_LAST | ||
55 | }; | ||
56 | #define WL1271_TM_ATTR_MAX (__WL1271_TM_ATTR_AFTER_LAST - 1) | ||
57 | |||
58 | static struct nla_policy wl1271_tm_policy[WL1271_TM_ATTR_MAX + 1] = { | ||
59 | [WL1271_TM_ATTR_CMD_ID] = { .type = NLA_U32 }, | ||
60 | [WL1271_TM_ATTR_ANSWER] = { .type = NLA_U8 }, | ||
61 | [WL1271_TM_ATTR_DATA] = { .type = NLA_BINARY, | ||
62 | .len = WL1271_TM_MAX_DATA_LENGTH }, | ||
63 | [WL1271_TM_ATTR_IE_ID] = { .type = NLA_U32 }, | ||
64 | [WL1271_TM_ATTR_PLT_MODE] = { .type = NLA_U32 }, | ||
65 | }; | ||
66 | |||
67 | |||
68 | static int wl1271_tm_cmd_test(struct wl1271 *wl, struct nlattr *tb[]) | ||
69 | { | ||
70 | int buf_len, ret, len; | ||
71 | struct sk_buff *skb; | ||
72 | void *buf; | ||
73 | u8 answer = 0; | ||
74 | |||
75 | wl1271_debug(DEBUG_TESTMODE, "testmode cmd test"); | ||
76 | |||
77 | if (!tb[WL1271_TM_ATTR_DATA]) | ||
78 | return -EINVAL; | ||
79 | |||
80 | buf = nla_data(tb[WL1271_TM_ATTR_DATA]); | ||
81 | buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); | ||
82 | |||
83 | if (tb[WL1271_TM_ATTR_ANSWER]) | ||
84 | answer = nla_get_u8(tb[WL1271_TM_ATTR_ANSWER]); | ||
85 | |||
86 | if (buf_len > sizeof(struct wl1271_command)) | ||
87 | return -EMSGSIZE; | ||
88 | |||
89 | mutex_lock(&wl->mutex); | ||
90 | ret = wl1271_cmd_test(wl, buf, buf_len, answer); | ||
91 | mutex_unlock(&wl->mutex); | ||
92 | |||
93 | if (ret < 0) { | ||
94 | wl1271_warning("testmode cmd test failed: %d", ret); | ||
95 | return ret; | ||
96 | } | ||
97 | |||
98 | if (answer) { | ||
99 | len = nla_total_size(buf_len); | ||
100 | skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, len); | ||
101 | if (!skb) | ||
102 | return -ENOMEM; | ||
103 | |||
104 | NLA_PUT(skb, WL1271_TM_ATTR_DATA, buf_len, buf); | ||
105 | ret = cfg80211_testmode_reply(skb); | ||
106 | if (ret < 0) | ||
107 | return ret; | ||
108 | } | ||
109 | |||
110 | return 0; | ||
111 | |||
112 | nla_put_failure: | ||
113 | kfree_skb(skb); | ||
114 | return -EMSGSIZE; | ||
115 | } | ||
116 | |||
117 | static int wl1271_tm_cmd_interrogate(struct wl1271 *wl, struct nlattr *tb[]) | ||
118 | { | ||
119 | int ret; | ||
120 | struct wl1271_command *cmd; | ||
121 | struct sk_buff *skb; | ||
122 | u8 ie_id; | ||
123 | |||
124 | wl1271_debug(DEBUG_TESTMODE, "testmode cmd interrogate"); | ||
125 | |||
126 | if (!tb[WL1271_TM_ATTR_IE_ID]) | ||
127 | return -EINVAL; | ||
128 | |||
129 | ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); | ||
130 | |||
131 | cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); | ||
132 | if (!cmd) | ||
133 | return -ENOMEM; | ||
134 | |||
135 | mutex_lock(&wl->mutex); | ||
136 | ret = wl1271_cmd_interrogate(wl, ie_id, cmd, sizeof(*cmd)); | ||
137 | mutex_unlock(&wl->mutex); | ||
138 | |||
139 | if (ret < 0) { | ||
140 | wl1271_warning("testmode cmd interrogate failed: %d", ret); | ||
141 | return ret; | ||
142 | } | ||
143 | |||
144 | skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, sizeof(*cmd)); | ||
145 | if (!skb) | ||
146 | return -ENOMEM; | ||
147 | |||
148 | NLA_PUT(skb, WL1271_TM_ATTR_DATA, sizeof(*cmd), cmd); | ||
149 | |||
150 | return 0; | ||
151 | |||
152 | nla_put_failure: | ||
153 | kfree_skb(skb); | ||
154 | return -EMSGSIZE; | ||
155 | } | ||
156 | |||
157 | static int wl1271_tm_cmd_configure(struct wl1271 *wl, struct nlattr *tb[]) | ||
158 | { | ||
159 | int buf_len, ret; | ||
160 | void *buf; | ||
161 | u8 ie_id; | ||
162 | |||
163 | wl1271_debug(DEBUG_TESTMODE, "testmode cmd configure"); | ||
164 | |||
165 | if (!tb[WL1271_TM_ATTR_DATA]) | ||
166 | return -EINVAL; | ||
167 | if (!tb[WL1271_TM_ATTR_IE_ID]) | ||
168 | return -EINVAL; | ||
169 | |||
170 | ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); | ||
171 | buf = nla_data(tb[WL1271_TM_ATTR_DATA]); | ||
172 | buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); | ||
173 | |||
174 | if (buf_len > sizeof(struct wl1271_command)) | ||
175 | return -EMSGSIZE; | ||
176 | |||
177 | mutex_lock(&wl->mutex); | ||
178 | ret = wl1271_cmd_configure(wl, ie_id, buf, buf_len); | ||
179 | mutex_unlock(&wl->mutex); | ||
180 | |||
181 | if (ret < 0) { | ||
182 | wl1271_warning("testmode cmd configure failed: %d", ret); | ||
183 | return ret; | ||
184 | } | ||
185 | |||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static int wl1271_tm_cmd_nvs_push(struct wl1271 *wl, struct nlattr *tb[]) | ||
190 | { | ||
191 | int ret = 0; | ||
192 | size_t len; | ||
193 | void *buf; | ||
194 | |||
195 | wl1271_debug(DEBUG_TESTMODE, "testmode cmd nvs push"); | ||
196 | |||
197 | if (!tb[WL1271_TM_ATTR_DATA]) | ||
198 | return -EINVAL; | ||
199 | |||
200 | buf = nla_data(tb[WL1271_TM_ATTR_DATA]); | ||
201 | len = nla_len(tb[WL1271_TM_ATTR_DATA]); | ||
202 | |||
203 | mutex_lock(&wl->mutex); | ||
204 | |||
205 | kfree(wl->nvs); | ||
206 | |||
207 | wl->nvs = kzalloc(sizeof(struct wl1271_nvs_file), GFP_KERNEL); | ||
208 | if (!wl->nvs) { | ||
209 | wl1271_error("could not allocate memory for the nvs file"); | ||
210 | ret = -ENOMEM; | ||
211 | goto out; | ||
212 | } | ||
213 | |||
214 | memcpy(wl->nvs, buf, len); | ||
215 | wl->nvs_len = len; | ||
216 | |||
217 | wl1271_debug(DEBUG_TESTMODE, "testmode pushed nvs"); | ||
218 | |||
219 | out: | ||
220 | mutex_unlock(&wl->mutex); | ||
221 | |||
222 | return ret; | ||
223 | } | ||
224 | |||
225 | static int wl1271_tm_cmd_set_plt_mode(struct wl1271 *wl, struct nlattr *tb[]) | ||
226 | { | ||
227 | u32 val; | ||
228 | int ret; | ||
229 | |||
230 | wl1271_debug(DEBUG_TESTMODE, "testmode cmd set plt mode"); | ||
231 | |||
232 | if (!tb[WL1271_TM_ATTR_PLT_MODE]) | ||
233 | return -EINVAL; | ||
234 | |||
235 | val = nla_get_u32(tb[WL1271_TM_ATTR_PLT_MODE]); | ||
236 | |||
237 | switch (val) { | ||
238 | case 0: | ||
239 | ret = wl1271_plt_stop(wl); | ||
240 | break; | ||
241 | case 1: | ||
242 | ret = wl1271_plt_start(wl); | ||
243 | break; | ||
244 | default: | ||
245 | ret = -EINVAL; | ||
246 | break; | ||
247 | } | ||
248 | |||
249 | return ret; | ||
250 | } | ||
251 | |||
252 | static int wl1271_tm_cmd_recover(struct wl1271 *wl, struct nlattr *tb[]) | ||
253 | { | ||
254 | wl1271_debug(DEBUG_TESTMODE, "testmode cmd recover"); | ||
255 | |||
256 | ieee80211_queue_work(wl->hw, &wl->recovery_work); | ||
257 | |||
258 | return 0; | ||
259 | } | ||
260 | |||
261 | int wl1271_tm_cmd(struct ieee80211_hw *hw, void *data, int len) | ||
262 | { | ||
263 | struct wl1271 *wl = hw->priv; | ||
264 | struct nlattr *tb[WL1271_TM_ATTR_MAX + 1]; | ||
265 | int err; | ||
266 | |||
267 | err = nla_parse(tb, WL1271_TM_ATTR_MAX, data, len, wl1271_tm_policy); | ||
268 | if (err) | ||
269 | return err; | ||
270 | |||
271 | if (!tb[WL1271_TM_ATTR_CMD_ID]) | ||
272 | return -EINVAL; | ||
273 | |||
274 | switch (nla_get_u32(tb[WL1271_TM_ATTR_CMD_ID])) { | ||
275 | case WL1271_TM_CMD_TEST: | ||
276 | return wl1271_tm_cmd_test(wl, tb); | ||
277 | case WL1271_TM_CMD_INTERROGATE: | ||
278 | return wl1271_tm_cmd_interrogate(wl, tb); | ||
279 | case WL1271_TM_CMD_CONFIGURE: | ||
280 | return wl1271_tm_cmd_configure(wl, tb); | ||
281 | case WL1271_TM_CMD_NVS_PUSH: | ||
282 | return wl1271_tm_cmd_nvs_push(wl, tb); | ||
283 | case WL1271_TM_CMD_SET_PLT_MODE: | ||
284 | return wl1271_tm_cmd_set_plt_mode(wl, tb); | ||
285 | case WL1271_TM_CMD_RECOVER: | ||
286 | return wl1271_tm_cmd_recover(wl, tb); | ||
287 | default: | ||
288 | return -EOPNOTSUPP; | ||
289 | } | ||
290 | } | ||