Commit | Line | Data |
---|---|---|
876c9d3a MT |
1 | /** |
2 | * This file contains the handling of command. | |
3 | * It prepares command and sends it to firmware when it is ready. | |
4 | */ | |
5 | ||
7919b89c | 6 | #include <linux/kfifo.h> |
e93156e7 | 7 | #include <linux/sched.h> |
5a0e3ad6 | 8 | #include <linux/slab.h> |
a45b6f4f | 9 | #include <linux/if_arp.h> |
e93156e7 | 10 | |
876c9d3a | 11 | #include "decl.h" |
e86dc1ca | 12 | #include "cfg.h" |
6e66f03f | 13 | #include "cmd.h" |
876c9d3a | 14 | |
e93156e7 | 15 | |
2fd6cfe3 | 16 | static struct cmd_ctrl_node *lbs_get_cmd_ctrl_node(struct lbs_private *priv); |
0d61d042 | 17 | |
8db4a2b9 HS |
18 | /** |
19 | * @brief Simple callback that copies response back into command | |
20 | * | |
21 | * @param priv A pointer to struct lbs_private structure | |
22 | * @param extra A pointer to the original command structure for which | |
23 | * 'resp' is a response | |
24 | * @param resp A pointer to the command response | |
25 | * | |
26 | * @return 0 on success, error on failure | |
27 | */ | |
28 | int lbs_cmd_copyback(struct lbs_private *priv, unsigned long extra, | |
29 | struct cmd_header *resp) | |
30 | { | |
31 | struct cmd_header *buf = (void *)extra; | |
32 | uint16_t copy_len; | |
33 | ||
34 | copy_len = min(le16_to_cpu(buf->size), le16_to_cpu(resp->size)); | |
35 | memcpy(buf, resp, copy_len); | |
36 | return 0; | |
37 | } | |
38 | EXPORT_SYMBOL_GPL(lbs_cmd_copyback); | |
39 | ||
40 | /** | |
41 | * @brief Simple callback that ignores the result. Use this if | |
42 | * you just want to send a command to the hardware, but don't | |
43 | * care for the result. | |
44 | * | |
45 | * @param priv ignored | |
46 | * @param extra ignored | |
47 | * @param resp ignored | |
48 | * | |
49 | * @return 0 for success | |
50 | */ | |
51 | static int lbs_cmd_async_callback(struct lbs_private *priv, unsigned long extra, | |
52 | struct cmd_header *resp) | |
53 | { | |
54 | return 0; | |
55 | } | |
56 | ||
57 | ||
876c9d3a | 58 | /** |
852e1f2a | 59 | * @brief Checks whether a command is allowed in Power Save mode |
876c9d3a MT |
60 | * |
61 | * @param command the command ID | |
852e1f2a | 62 | * @return 1 if allowed, 0 if not allowed |
876c9d3a | 63 | */ |
852e1f2a | 64 | static u8 is_command_allowed_in_ps(u16 cmd) |
876c9d3a | 65 | { |
852e1f2a DW |
66 | switch (cmd) { |
67 | case CMD_802_11_RSSI: | |
68 | return 1; | |
66fceb69 AK |
69 | case CMD_802_11_HOST_SLEEP_CFG: |
70 | return 1; | |
852e1f2a DW |
71 | default: |
72 | break; | |
876c9d3a | 73 | } |
876c9d3a MT |
74 | return 0; |
75 | } | |
76 | ||
63f275df AK |
77 | /** |
78 | * @brief This function checks if the command is allowed. | |
79 | * | |
80 | * @param priv A pointer to lbs_private structure | |
81 | * @return allowed or not allowed. | |
82 | */ | |
83 | ||
84 | static int lbs_is_cmd_allowed(struct lbs_private *priv) | |
85 | { | |
86 | int ret = 1; | |
87 | ||
88 | lbs_deb_enter(LBS_DEB_CMD); | |
89 | ||
90 | if (!priv->is_auto_deep_sleep_enabled) { | |
91 | if (priv->is_deep_sleep) { | |
92 | lbs_deb_cmd("command not allowed in deep sleep\n"); | |
93 | ret = 0; | |
94 | } | |
95 | } | |
96 | ||
97 | lbs_deb_leave(LBS_DEB_CMD); | |
98 | return ret; | |
99 | } | |
100 | ||
6e66f03f DW |
101 | /** |
102 | * @brief Updates the hardware details like MAC address and regulatory region | |
103 | * | |
104 | * @param priv A pointer to struct lbs_private structure | |
105 | * | |
106 | * @return 0 on success, error on failure | |
107 | */ | |
108 | int lbs_update_hw_spec(struct lbs_private *priv) | |
876c9d3a | 109 | { |
6e66f03f DW |
110 | struct cmd_ds_get_hw_spec cmd; |
111 | int ret = -1; | |
112 | u32 i; | |
876c9d3a | 113 | |
9012b28a | 114 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 115 | |
6e66f03f DW |
116 | memset(&cmd, 0, sizeof(cmd)); |
117 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); | |
118 | memcpy(cmd.permanentaddr, priv->current_addr, ETH_ALEN); | |
689442dc | 119 | ret = lbs_cmd_with_response(priv, CMD_GET_HW_SPEC, &cmd); |
6e66f03f DW |
120 | if (ret) |
121 | goto out; | |
122 | ||
123 | priv->fwcapinfo = le32_to_cpu(cmd.fwcapinfo); | |
6e66f03f | 124 | |
dac10a9f HS |
125 | /* The firmware release is in an interesting format: the patch |
126 | * level is in the most significant nibble ... so fix that: */ | |
127 | priv->fwrelease = le32_to_cpu(cmd.fwrelease); | |
128 | priv->fwrelease = (priv->fwrelease << 8) | | |
129 | (priv->fwrelease >> 24 & 0xff); | |
130 | ||
131 | /* Some firmware capabilities: | |
132 | * CF card firmware 5.0.16p0: cap 0x00000303 | |
133 | * USB dongle firmware 5.110.17p2: cap 0x00000303 | |
134 | */ | |
e174961c JB |
135 | lbs_pr_info("%pM, fw %u.%u.%up%u, cap 0x%08x\n", |
136 | cmd.permanentaddr, | |
dac10a9f HS |
137 | priv->fwrelease >> 24 & 0xff, |
138 | priv->fwrelease >> 16 & 0xff, | |
139 | priv->fwrelease >> 8 & 0xff, | |
140 | priv->fwrelease & 0xff, | |
141 | priv->fwcapinfo); | |
6e66f03f DW |
142 | lbs_deb_cmd("GET_HW_SPEC: hardware interface 0x%x, hardware spec 0x%04x\n", |
143 | cmd.hwifversion, cmd.version); | |
144 | ||
145 | /* Clamp region code to 8-bit since FW spec indicates that it should | |
146 | * only ever be 8-bit, even though the field size is 16-bit. Some firmware | |
147 | * returns non-zero high 8 bits here. | |
15483996 MV |
148 | * |
149 | * Firmware version 4.0.102 used in CF8381 has region code shifted. We | |
150 | * need to check for this problem and handle it properly. | |
6e66f03f | 151 | */ |
15483996 MV |
152 | if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V4) |
153 | priv->regioncode = (le16_to_cpu(cmd.regioncode) >> 8) & 0xFF; | |
154 | else | |
155 | priv->regioncode = le16_to_cpu(cmd.regioncode) & 0xFF; | |
6e66f03f DW |
156 | |
157 | for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) { | |
158 | /* use the region code to search for the index */ | |
159 | if (priv->regioncode == lbs_region_code_to_index[i]) | |
160 | break; | |
161 | } | |
162 | ||
163 | /* if it's unidentified region code, use the default (USA) */ | |
164 | if (i >= MRVDRV_MAX_REGION_CODE) { | |
165 | priv->regioncode = 0x10; | |
166 | lbs_pr_info("unidentified region code; using the default (USA)\n"); | |
167 | } | |
168 | ||
169 | if (priv->current_addr[0] == 0xff) | |
170 | memmove(priv->current_addr, cmd.permanentaddr, ETH_ALEN); | |
876c9d3a | 171 | |
6e66f03f DW |
172 | memcpy(priv->dev->dev_addr, priv->current_addr, ETH_ALEN); |
173 | if (priv->mesh_dev) | |
174 | memcpy(priv->mesh_dev->dev_addr, priv->current_addr, ETH_ALEN); | |
175 | ||
6e66f03f | 176 | out: |
9012b28a | 177 | lbs_deb_leave(LBS_DEB_CMD); |
6e66f03f | 178 | return ret; |
876c9d3a MT |
179 | } |
180 | ||
66fceb69 AK |
181 | static int lbs_ret_host_sleep_cfg(struct lbs_private *priv, unsigned long dummy, |
182 | struct cmd_header *resp) | |
183 | { | |
184 | lbs_deb_enter(LBS_DEB_CMD); | |
1311843c | 185 | if (priv->is_host_sleep_activated) { |
66fceb69 AK |
186 | priv->is_host_sleep_configured = 0; |
187 | if (priv->psstate == PS_STATE_FULL_POWER) { | |
188 | priv->is_host_sleep_activated = 0; | |
189 | wake_up_interruptible(&priv->host_sleep_q); | |
190 | } | |
191 | } else { | |
192 | priv->is_host_sleep_configured = 1; | |
193 | } | |
194 | lbs_deb_leave(LBS_DEB_CMD); | |
195 | return 0; | |
196 | } | |
197 | ||
582c1b53 AN |
198 | int lbs_host_sleep_cfg(struct lbs_private *priv, uint32_t criteria, |
199 | struct wol_config *p_wol_config) | |
6ce4fd2a DW |
200 | { |
201 | struct cmd_ds_host_sleep cmd_config; | |
202 | int ret; | |
203 | ||
9fae899c | 204 | cmd_config.hdr.size = cpu_to_le16(sizeof(cmd_config)); |
6ce4fd2a | 205 | cmd_config.criteria = cpu_to_le32(criteria); |
506e9025 DW |
206 | cmd_config.gpio = priv->wol_gpio; |
207 | cmd_config.gap = priv->wol_gap; | |
6ce4fd2a | 208 | |
582c1b53 AN |
209 | if (p_wol_config != NULL) |
210 | memcpy((uint8_t *)&cmd_config.wol_conf, (uint8_t *)p_wol_config, | |
211 | sizeof(struct wol_config)); | |
212 | else | |
213 | cmd_config.wol_conf.action = CMD_ACT_ACTION_NONE; | |
214 | ||
66fceb69 AK |
215 | ret = __lbs_cmd(priv, CMD_802_11_HOST_SLEEP_CFG, &cmd_config.hdr, |
216 | le16_to_cpu(cmd_config.hdr.size), | |
217 | lbs_ret_host_sleep_cfg, 0); | |
506e9025 | 218 | if (!ret) { |
66fceb69 | 219 | if (p_wol_config) |
582c1b53 AN |
220 | memcpy((uint8_t *) p_wol_config, |
221 | (uint8_t *)&cmd_config.wol_conf, | |
222 | sizeof(struct wol_config)); | |
506e9025 | 223 | } else { |
6ce4fd2a | 224 | lbs_pr_info("HOST_SLEEP_CFG failed %d\n", ret); |
6ce4fd2a | 225 | } |
506e9025 | 226 | |
6ce4fd2a DW |
227 | return ret; |
228 | } | |
229 | EXPORT_SYMBOL_GPL(lbs_host_sleep_cfg); | |
230 | ||
e98a88dd | 231 | static int lbs_cmd_802_11_ps_mode(struct cmd_ds_command *cmd, |
876c9d3a MT |
232 | u16 cmd_action) |
233 | { | |
234 | struct cmd_ds_802_11_ps_mode *psm = &cmd->params.psmode; | |
876c9d3a | 235 | |
9012b28a | 236 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 237 | |
0aef64d7 | 238 | cmd->command = cpu_to_le16(CMD_802_11_PS_MODE); |
981f187b | 239 | cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_ps_mode) + |
8ec97cc8 | 240 | sizeof(struct cmd_header)); |
876c9d3a MT |
241 | psm->action = cpu_to_le16(cmd_action); |
242 | psm->multipledtim = 0; | |
981f187b | 243 | switch (cmd_action) { |
0aef64d7 | 244 | case CMD_SUBCMD_ENTER_PS: |
9012b28a | 245 | lbs_deb_cmd("PS command:" "SubCode- Enter PS\n"); |
876c9d3a | 246 | |
252cf0d1 | 247 | psm->locallisteninterval = 0; |
97605c3e | 248 | psm->nullpktinterval = 0; |
876c9d3a | 249 | psm->multipledtim = |
56c4656e | 250 | cpu_to_le16(MRVDRV_DEFAULT_MULTIPLE_DTIM); |
876c9d3a MT |
251 | break; |
252 | ||
0aef64d7 | 253 | case CMD_SUBCMD_EXIT_PS: |
9012b28a | 254 | lbs_deb_cmd("PS command:" "SubCode- Exit PS\n"); |
876c9d3a MT |
255 | break; |
256 | ||
0aef64d7 | 257 | case CMD_SUBCMD_SLEEP_CONFIRMED: |
9012b28a | 258 | lbs_deb_cmd("PS command: SubCode- sleep confirm\n"); |
876c9d3a MT |
259 | break; |
260 | ||
261 | default: | |
262 | break; | |
263 | } | |
264 | ||
9012b28a | 265 | lbs_deb_leave(LBS_DEB_CMD); |
876c9d3a MT |
266 | return 0; |
267 | } | |
268 | ||
3fbe104c DW |
269 | int lbs_cmd_802_11_sleep_params(struct lbs_private *priv, uint16_t cmd_action, |
270 | struct sleep_params *sp) | |
876c9d3a | 271 | { |
3fbe104c DW |
272 | struct cmd_ds_802_11_sleep_params cmd; |
273 | int ret; | |
876c9d3a | 274 | |
9012b28a | 275 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 276 | |
0aef64d7 | 277 | if (cmd_action == CMD_ACT_GET) { |
3fbe104c DW |
278 | memset(&cmd, 0, sizeof(cmd)); |
279 | } else { | |
280 | cmd.error = cpu_to_le16(sp->sp_error); | |
281 | cmd.offset = cpu_to_le16(sp->sp_offset); | |
282 | cmd.stabletime = cpu_to_le16(sp->sp_stabletime); | |
283 | cmd.calcontrol = sp->sp_calcontrol; | |
284 | cmd.externalsleepclk = sp->sp_extsleepclk; | |
285 | cmd.reserved = cpu_to_le16(sp->sp_reserved); | |
876c9d3a | 286 | } |
3fbe104c DW |
287 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); |
288 | cmd.action = cpu_to_le16(cmd_action); | |
876c9d3a | 289 | |
3fbe104c DW |
290 | ret = lbs_cmd_with_response(priv, CMD_802_11_SLEEP_PARAMS, &cmd); |
291 | ||
292 | if (!ret) { | |
293 | lbs_deb_cmd("error 0x%x, offset 0x%x, stabletime 0x%x, " | |
294 | "calcontrol 0x%x extsleepclk 0x%x\n", | |
295 | le16_to_cpu(cmd.error), le16_to_cpu(cmd.offset), | |
296 | le16_to_cpu(cmd.stabletime), cmd.calcontrol, | |
297 | cmd.externalsleepclk); | |
298 | ||
299 | sp->sp_error = le16_to_cpu(cmd.error); | |
300 | sp->sp_offset = le16_to_cpu(cmd.offset); | |
301 | sp->sp_stabletime = le16_to_cpu(cmd.stabletime); | |
302 | sp->sp_calcontrol = cmd.calcontrol; | |
303 | sp->sp_extsleepclk = cmd.externalsleepclk; | |
304 | sp->sp_reserved = le16_to_cpu(cmd.reserved); | |
305 | } | |
306 | ||
307 | lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); | |
876c9d3a MT |
308 | return 0; |
309 | } | |
310 | ||
49125454 AK |
311 | static int lbs_wait_for_ds_awake(struct lbs_private *priv) |
312 | { | |
313 | int ret = 0; | |
314 | ||
315 | lbs_deb_enter(LBS_DEB_CMD); | |
316 | ||
317 | if (priv->is_deep_sleep) { | |
318 | if (!wait_event_interruptible_timeout(priv->ds_awake_q, | |
319 | !priv->is_deep_sleep, (10 * HZ))) { | |
320 | lbs_pr_err("ds_awake_q: timer expired\n"); | |
321 | ret = -1; | |
322 | } | |
323 | } | |
324 | ||
325 | lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); | |
326 | return ret; | |
327 | } | |
328 | ||
329 | int lbs_set_deep_sleep(struct lbs_private *priv, int deep_sleep) | |
330 | { | |
331 | int ret = 0; | |
332 | ||
333 | lbs_deb_enter(LBS_DEB_CMD); | |
334 | ||
335 | if (deep_sleep) { | |
336 | if (priv->is_deep_sleep != 1) { | |
337 | lbs_deb_cmd("deep sleep: sleep\n"); | |
338 | BUG_ON(!priv->enter_deep_sleep); | |
339 | ret = priv->enter_deep_sleep(priv); | |
340 | if (!ret) { | |
341 | netif_stop_queue(priv->dev); | |
342 | netif_carrier_off(priv->dev); | |
343 | } | |
344 | } else { | |
345 | lbs_pr_err("deep sleep: already enabled\n"); | |
346 | } | |
347 | } else { | |
348 | if (priv->is_deep_sleep) { | |
349 | lbs_deb_cmd("deep sleep: wakeup\n"); | |
350 | BUG_ON(!priv->exit_deep_sleep); | |
351 | ret = priv->exit_deep_sleep(priv); | |
352 | if (!ret) { | |
353 | ret = lbs_wait_for_ds_awake(priv); | |
354 | if (ret) | |
355 | lbs_pr_err("deep sleep: wakeup" | |
356 | "failed\n"); | |
357 | } | |
358 | } | |
359 | } | |
360 | ||
361 | lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); | |
362 | return ret; | |
363 | } | |
364 | ||
1311843c AK |
365 | static int lbs_ret_host_sleep_activate(struct lbs_private *priv, |
366 | unsigned long dummy, | |
367 | struct cmd_header *cmd) | |
368 | { | |
369 | lbs_deb_enter(LBS_DEB_FW); | |
370 | priv->is_host_sleep_activated = 1; | |
371 | wake_up_interruptible(&priv->host_sleep_q); | |
372 | lbs_deb_leave(LBS_DEB_FW); | |
373 | return 0; | |
374 | } | |
375 | ||
376 | int lbs_set_host_sleep(struct lbs_private *priv, int host_sleep) | |
377 | { | |
378 | struct cmd_header cmd; | |
379 | int ret = 0; | |
380 | uint32_t criteria = EHS_REMOVE_WAKEUP; | |
381 | ||
382 | lbs_deb_enter(LBS_DEB_CMD); | |
383 | ||
384 | if (host_sleep) { | |
385 | if (priv->is_host_sleep_activated != 1) { | |
386 | memset(&cmd, 0, sizeof(cmd)); | |
387 | ret = lbs_host_sleep_cfg(priv, priv->wol_criteria, | |
388 | (struct wol_config *)NULL); | |
389 | if (ret) { | |
390 | lbs_pr_info("Host sleep configuration failed: " | |
391 | "%d\n", ret); | |
392 | return ret; | |
393 | } | |
394 | if (priv->psstate == PS_STATE_FULL_POWER) { | |
395 | ret = __lbs_cmd(priv, | |
396 | CMD_802_11_HOST_SLEEP_ACTIVATE, | |
397 | &cmd, | |
398 | sizeof(cmd), | |
399 | lbs_ret_host_sleep_activate, 0); | |
400 | if (ret) | |
401 | lbs_pr_info("HOST_SLEEP_ACTIVATE " | |
402 | "failed: %d\n", ret); | |
403 | } | |
404 | ||
405 | if (!wait_event_interruptible_timeout( | |
406 | priv->host_sleep_q, | |
407 | priv->is_host_sleep_activated, | |
408 | (10 * HZ))) { | |
409 | lbs_pr_err("host_sleep_q: timer expired\n"); | |
410 | ret = -1; | |
411 | } | |
412 | } else { | |
413 | lbs_pr_err("host sleep: already enabled\n"); | |
414 | } | |
415 | } else { | |
416 | if (priv->is_host_sleep_activated) | |
417 | ret = lbs_host_sleep_cfg(priv, criteria, | |
418 | (struct wol_config *)NULL); | |
419 | } | |
420 | ||
421 | return ret; | |
422 | } | |
423 | ||
39fcf7a3 DW |
424 | /** |
425 | * @brief Set an SNMP MIB value | |
426 | * | |
427 | * @param priv A pointer to struct lbs_private structure | |
428 | * @param oid The OID to set in the firmware | |
429 | * @param val Value to set the OID to | |
430 | * | |
431 | * @return 0 on success, error on failure | |
432 | */ | |
433 | int lbs_set_snmp_mib(struct lbs_private *priv, u32 oid, u16 val) | |
876c9d3a | 434 | { |
39fcf7a3 DW |
435 | struct cmd_ds_802_11_snmp_mib cmd; |
436 | int ret; | |
876c9d3a | 437 | |
9012b28a | 438 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 439 | |
39fcf7a3 DW |
440 | memset(&cmd, 0, sizeof (cmd)); |
441 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); | |
442 | cmd.action = cpu_to_le16(CMD_ACT_SET); | |
443 | cmd.oid = cpu_to_le16((u16) oid); | |
876c9d3a | 444 | |
39fcf7a3 DW |
445 | switch (oid) { |
446 | case SNMP_MIB_OID_BSS_TYPE: | |
447 | cmd.bufsize = cpu_to_le16(sizeof(u8)); | |
fef0640e | 448 | cmd.value[0] = val; |
39fcf7a3 DW |
449 | break; |
450 | case SNMP_MIB_OID_11D_ENABLE: | |
451 | case SNMP_MIB_OID_FRAG_THRESHOLD: | |
452 | case SNMP_MIB_OID_RTS_THRESHOLD: | |
453 | case SNMP_MIB_OID_SHORT_RETRY_LIMIT: | |
454 | case SNMP_MIB_OID_LONG_RETRY_LIMIT: | |
455 | cmd.bufsize = cpu_to_le16(sizeof(u16)); | |
456 | *((__le16 *)(&cmd.value)) = cpu_to_le16(val); | |
876c9d3a | 457 | break; |
39fcf7a3 DW |
458 | default: |
459 | lbs_deb_cmd("SNMP_CMD: (set) unhandled OID 0x%x\n", oid); | |
460 | ret = -EINVAL; | |
461 | goto out; | |
876c9d3a MT |
462 | } |
463 | ||
39fcf7a3 DW |
464 | lbs_deb_cmd("SNMP_CMD: (set) oid 0x%x, oid size 0x%x, value 0x%x\n", |
465 | le16_to_cpu(cmd.oid), le16_to_cpu(cmd.bufsize), val); | |
876c9d3a | 466 | |
39fcf7a3 | 467 | ret = lbs_cmd_with_response(priv, CMD_802_11_SNMP_MIB, &cmd); |
876c9d3a | 468 | |
39fcf7a3 DW |
469 | out: |
470 | lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); | |
471 | return ret; | |
472 | } | |
876c9d3a | 473 | |
39fcf7a3 DW |
474 | /** |
475 | * @brief Get an SNMP MIB value | |
476 | * | |
477 | * @param priv A pointer to struct lbs_private structure | |
478 | * @param oid The OID to retrieve from the firmware | |
479 | * @param out_val Location for the returned value | |
480 | * | |
481 | * @return 0 on success, error on failure | |
482 | */ | |
483 | int lbs_get_snmp_mib(struct lbs_private *priv, u32 oid, u16 *out_val) | |
484 | { | |
485 | struct cmd_ds_802_11_snmp_mib cmd; | |
486 | int ret; | |
876c9d3a | 487 | |
39fcf7a3 | 488 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 489 | |
39fcf7a3 DW |
490 | memset(&cmd, 0, sizeof (cmd)); |
491 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); | |
492 | cmd.action = cpu_to_le16(CMD_ACT_GET); | |
493 | cmd.oid = cpu_to_le16(oid); | |
876c9d3a | 494 | |
39fcf7a3 DW |
495 | ret = lbs_cmd_with_response(priv, CMD_802_11_SNMP_MIB, &cmd); |
496 | if (ret) | |
497 | goto out; | |
876c9d3a | 498 | |
39fcf7a3 DW |
499 | switch (le16_to_cpu(cmd.bufsize)) { |
500 | case sizeof(u8): | |
fef0640e | 501 | *out_val = cmd.value[0]; |
39fcf7a3 DW |
502 | break; |
503 | case sizeof(u16): | |
504 | *out_val = le16_to_cpu(*((__le16 *)(&cmd.value))); | |
876c9d3a MT |
505 | break; |
506 | default: | |
39fcf7a3 DW |
507 | lbs_deb_cmd("SNMP_CMD: (get) unhandled OID 0x%x size %d\n", |
508 | oid, le16_to_cpu(cmd.bufsize)); | |
876c9d3a MT |
509 | break; |
510 | } | |
511 | ||
39fcf7a3 DW |
512 | out: |
513 | lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); | |
514 | return ret; | |
876c9d3a MT |
515 | } |
516 | ||
87c8c72d DW |
517 | /** |
518 | * @brief Get the min, max, and current TX power | |
519 | * | |
520 | * @param priv A pointer to struct lbs_private structure | |
521 | * @param curlevel Current power level in dBm | |
522 | * @param minlevel Minimum supported power level in dBm (optional) | |
523 | * @param maxlevel Maximum supported power level in dBm (optional) | |
524 | * | |
525 | * @return 0 on success, error on failure | |
526 | */ | |
527 | int lbs_get_tx_power(struct lbs_private *priv, s16 *curlevel, s16 *minlevel, | |
528 | s16 *maxlevel) | |
876c9d3a | 529 | { |
87c8c72d DW |
530 | struct cmd_ds_802_11_rf_tx_power cmd; |
531 | int ret; | |
876c9d3a | 532 | |
9012b28a | 533 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 534 | |
87c8c72d DW |
535 | memset(&cmd, 0, sizeof(cmd)); |
536 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); | |
537 | cmd.action = cpu_to_le16(CMD_ACT_GET); | |
538 | ||
539 | ret = lbs_cmd_with_response(priv, CMD_802_11_RF_TX_POWER, &cmd); | |
540 | if (ret == 0) { | |
541 | *curlevel = le16_to_cpu(cmd.curlevel); | |
542 | if (minlevel) | |
87bf24f3 | 543 | *minlevel = cmd.minlevel; |
87c8c72d | 544 | if (maxlevel) |
87bf24f3 | 545 | *maxlevel = cmd.maxlevel; |
87c8c72d | 546 | } |
876c9d3a | 547 | |
87c8c72d DW |
548 | lbs_deb_leave(LBS_DEB_CMD); |
549 | return ret; | |
550 | } | |
876c9d3a | 551 | |
87c8c72d DW |
552 | /** |
553 | * @brief Set the TX power | |
554 | * | |
555 | * @param priv A pointer to struct lbs_private structure | |
556 | * @param dbm The desired power level in dBm | |
557 | * | |
558 | * @return 0 on success, error on failure | |
559 | */ | |
560 | int lbs_set_tx_power(struct lbs_private *priv, s16 dbm) | |
561 | { | |
562 | struct cmd_ds_802_11_rf_tx_power cmd; | |
563 | int ret; | |
876c9d3a | 564 | |
87c8c72d | 565 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 566 | |
87c8c72d DW |
567 | memset(&cmd, 0, sizeof(cmd)); |
568 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); | |
569 | cmd.action = cpu_to_le16(CMD_ACT_SET); | |
570 | cmd.curlevel = cpu_to_le16(dbm); | |
876c9d3a | 571 | |
87c8c72d DW |
572 | lbs_deb_cmd("SET_RF_TX_POWER: %d dBm\n", dbm); |
573 | ||
574 | ret = lbs_cmd_with_response(priv, CMD_802_11_RF_TX_POWER, &cmd); | |
9012b28a HS |
575 | |
576 | lbs_deb_leave(LBS_DEB_CMD); | |
87c8c72d | 577 | return ret; |
876c9d3a MT |
578 | } |
579 | ||
a45b6f4f DW |
580 | /** |
581 | * @brief Enable or disable monitor mode (only implemented on OLPC usb8388 FW) | |
582 | * | |
583 | * @param priv A pointer to struct lbs_private structure | |
584 | * @param enable 1 to enable monitor mode, 0 to disable | |
585 | * | |
586 | * @return 0 on success, error on failure | |
587 | */ | |
588 | int lbs_set_monitor_mode(struct lbs_private *priv, int enable) | |
965f8bbc | 589 | { |
a45b6f4f DW |
590 | struct cmd_ds_802_11_monitor_mode cmd; |
591 | int ret; | |
965f8bbc | 592 | |
a45b6f4f DW |
593 | memset(&cmd, 0, sizeof(cmd)); |
594 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); | |
595 | cmd.action = cpu_to_le16(CMD_ACT_SET); | |
596 | if (enable) | |
597 | cmd.mode = cpu_to_le16(0x1); | |
965f8bbc | 598 | |
a45b6f4f DW |
599 | lbs_deb_cmd("SET_MONITOR_MODE: %d\n", enable); |
600 | ||
601 | ret = lbs_cmd_with_response(priv, CMD_802_11_MONITOR_MODE, &cmd); | |
602 | if (ret == 0) { | |
603 | priv->dev->type = enable ? ARPHRD_IEEE80211_RADIOTAP : | |
604 | ARPHRD_ETHER; | |
965f8bbc LCC |
605 | } |
606 | ||
a45b6f4f DW |
607 | lbs_deb_leave(LBS_DEB_CMD); |
608 | return ret; | |
965f8bbc LCC |
609 | } |
610 | ||
2dd4b262 DW |
611 | /** |
612 | * @brief Get the radio channel | |
613 | * | |
614 | * @param priv A pointer to struct lbs_private structure | |
615 | * | |
616 | * @return The channel on success, error on failure | |
617 | */ | |
a3cbfb08 | 618 | static int lbs_get_channel(struct lbs_private *priv) |
876c9d3a | 619 | { |
2dd4b262 DW |
620 | struct cmd_ds_802_11_rf_channel cmd; |
621 | int ret = 0; | |
876c9d3a | 622 | |
8ff12da1 | 623 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 624 | |
8d0c7fad | 625 | memset(&cmd, 0, sizeof(cmd)); |
2dd4b262 DW |
626 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); |
627 | cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_GET); | |
876c9d3a | 628 | |
689442dc | 629 | ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd); |
2dd4b262 DW |
630 | if (ret) |
631 | goto out; | |
876c9d3a | 632 | |
cb182a60 DW |
633 | ret = le16_to_cpu(cmd.channel); |
634 | lbs_deb_cmd("current radio channel is %d\n", ret); | |
2dd4b262 DW |
635 | |
636 | out: | |
637 | lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); | |
638 | return ret; | |
639 | } | |
640 | ||
73ab1f25 HS |
641 | int lbs_update_channel(struct lbs_private *priv) |
642 | { | |
643 | int ret; | |
644 | ||
645 | /* the channel in f/w could be out of sync; get the current channel */ | |
646 | lbs_deb_enter(LBS_DEB_ASSOC); | |
647 | ||
648 | ret = lbs_get_channel(priv); | |
649 | if (ret > 0) { | |
c14951fe | 650 | priv->channel = ret; |
73ab1f25 HS |
651 | ret = 0; |
652 | } | |
653 | lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); | |
654 | return ret; | |
655 | } | |
656 | ||
2dd4b262 DW |
657 | /** |
658 | * @brief Set the radio channel | |
659 | * | |
660 | * @param priv A pointer to struct lbs_private structure | |
661 | * @param channel The desired channel, or 0 to clear a locked channel | |
662 | * | |
663 | * @return 0 on success, error on failure | |
664 | */ | |
665 | int lbs_set_channel(struct lbs_private *priv, u8 channel) | |
666 | { | |
667 | struct cmd_ds_802_11_rf_channel cmd; | |
96d46d5d | 668 | #ifdef DEBUG |
c14951fe | 669 | u8 old_channel = priv->channel; |
96d46d5d | 670 | #endif |
2dd4b262 DW |
671 | int ret = 0; |
672 | ||
673 | lbs_deb_enter(LBS_DEB_CMD); | |
674 | ||
8d0c7fad | 675 | memset(&cmd, 0, sizeof(cmd)); |
2dd4b262 DW |
676 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); |
677 | cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_SET); | |
678 | cmd.channel = cpu_to_le16(channel); | |
679 | ||
689442dc | 680 | ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd); |
2dd4b262 DW |
681 | if (ret) |
682 | goto out; | |
683 | ||
c14951fe | 684 | priv->channel = (uint8_t) le16_to_cpu(cmd.channel); |
cb182a60 | 685 | lbs_deb_cmd("channel switch from %d to %d\n", old_channel, |
c14951fe | 686 | priv->channel); |
2dd4b262 DW |
687 | |
688 | out: | |
689 | lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); | |
690 | return ret; | |
876c9d3a MT |
691 | } |
692 | ||
e98a88dd | 693 | static int lbs_cmd_reg_access(struct cmd_ds_command *cmdptr, |
876c9d3a MT |
694 | u8 cmd_action, void *pdata_buf) |
695 | { | |
10078321 | 696 | struct lbs_offset_value *offval; |
876c9d3a | 697 | |
9012b28a | 698 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 699 | |
10078321 | 700 | offval = (struct lbs_offset_value *)pdata_buf; |
876c9d3a | 701 | |
c2df2efe | 702 | switch (le16_to_cpu(cmdptr->command)) { |
0aef64d7 | 703 | case CMD_MAC_REG_ACCESS: |
876c9d3a MT |
704 | { |
705 | struct cmd_ds_mac_reg_access *macreg; | |
706 | ||
707 | cmdptr->size = | |
981f187b | 708 | cpu_to_le16(sizeof (struct cmd_ds_mac_reg_access) |
8ec97cc8 | 709 | + sizeof(struct cmd_header)); |
876c9d3a MT |
710 | macreg = |
711 | (struct cmd_ds_mac_reg_access *)&cmdptr->params. | |
712 | macreg; | |
713 | ||
714 | macreg->action = cpu_to_le16(cmd_action); | |
715 | macreg->offset = cpu_to_le16((u16) offval->offset); | |
716 | macreg->value = cpu_to_le32(offval->value); | |
717 | ||
718 | break; | |
719 | } | |
720 | ||
0aef64d7 | 721 | case CMD_BBP_REG_ACCESS: |
876c9d3a MT |
722 | { |
723 | struct cmd_ds_bbp_reg_access *bbpreg; | |
724 | ||
725 | cmdptr->size = | |
726 | cpu_to_le16(sizeof | |
727 | (struct cmd_ds_bbp_reg_access) | |
8ec97cc8 | 728 | + sizeof(struct cmd_header)); |
876c9d3a MT |
729 | bbpreg = |
730 | (struct cmd_ds_bbp_reg_access *)&cmdptr->params. | |
731 | bbpreg; | |
732 | ||
733 | bbpreg->action = cpu_to_le16(cmd_action); | |
734 | bbpreg->offset = cpu_to_le16((u16) offval->offset); | |
735 | bbpreg->value = (u8) offval->value; | |
736 | ||
737 | break; | |
738 | } | |
739 | ||
0aef64d7 | 740 | case CMD_RF_REG_ACCESS: |
876c9d3a MT |
741 | { |
742 | struct cmd_ds_rf_reg_access *rfreg; | |
743 | ||
744 | cmdptr->size = | |
745 | cpu_to_le16(sizeof | |
746 | (struct cmd_ds_rf_reg_access) + | |
8ec97cc8 | 747 | sizeof(struct cmd_header)); |
876c9d3a MT |
748 | rfreg = |
749 | (struct cmd_ds_rf_reg_access *)&cmdptr->params. | |
750 | rfreg; | |
751 | ||
752 | rfreg->action = cpu_to_le16(cmd_action); | |
753 | rfreg->offset = cpu_to_le16((u16) offval->offset); | |
754 | rfreg->value = (u8) offval->value; | |
755 | ||
756 | break; | |
757 | } | |
758 | ||
759 | default: | |
760 | break; | |
761 | } | |
762 | ||
9012b28a | 763 | lbs_deb_leave(LBS_DEB_CMD); |
876c9d3a MT |
764 | return 0; |
765 | } | |
766 | ||
681ffbb7 DW |
767 | static void lbs_queue_cmd(struct lbs_private *priv, |
768 | struct cmd_ctrl_node *cmdnode) | |
876c9d3a MT |
769 | { |
770 | unsigned long flags; | |
681ffbb7 | 771 | int addtail = 1; |
876c9d3a | 772 | |
8ff12da1 | 773 | lbs_deb_enter(LBS_DEB_HOST); |
876c9d3a | 774 | |
c4ab4127 DW |
775 | if (!cmdnode) { |
776 | lbs_deb_host("QUEUE_CMD: cmdnode is NULL\n"); | |
876c9d3a MT |
777 | goto done; |
778 | } | |
d9896ee1 DW |
779 | if (!cmdnode->cmdbuf->size) { |
780 | lbs_deb_host("DNLD_CMD: cmd size is zero\n"); | |
781 | goto done; | |
782 | } | |
ae125bf8 | 783 | cmdnode->result = 0; |
876c9d3a MT |
784 | |
785 | /* Exit_PS command needs to be queued in the header always. */ | |
ddac4526 | 786 | if (le16_to_cpu(cmdnode->cmdbuf->command) == CMD_802_11_PS_MODE) { |
38bfab1a | 787 | struct cmd_ds_802_11_ps_mode *psm = (void *) &cmdnode->cmdbuf[1]; |
ddac4526 | 788 | |
0aef64d7 | 789 | if (psm->action == cpu_to_le16(CMD_SUBCMD_EXIT_PS)) { |
aa21c004 | 790 | if (priv->psstate != PS_STATE_FULL_POWER) |
876c9d3a MT |
791 | addtail = 0; |
792 | } | |
793 | } | |
794 | ||
66fceb69 AK |
795 | if (le16_to_cpu(cmdnode->cmdbuf->command) == |
796 | CMD_802_11_WAKEUP_CONFIRM) | |
797 | addtail = 0; | |
798 | ||
aa21c004 | 799 | spin_lock_irqsave(&priv->driver_lock, flags); |
876c9d3a | 800 | |
ac47246e | 801 | if (addtail) |
aa21c004 | 802 | list_add_tail(&cmdnode->list, &priv->cmdpendingq); |
ac47246e | 803 | else |
aa21c004 | 804 | list_add(&cmdnode->list, &priv->cmdpendingq); |
876c9d3a | 805 | |
aa21c004 | 806 | spin_unlock_irqrestore(&priv->driver_lock, flags); |
876c9d3a | 807 | |
8ff12da1 | 808 | lbs_deb_host("QUEUE_CMD: inserted command 0x%04x into cmdpendingq\n", |
c4ab4127 | 809 | le16_to_cpu(cmdnode->cmdbuf->command)); |
876c9d3a MT |
810 | |
811 | done: | |
8ff12da1 | 812 | lbs_deb_leave(LBS_DEB_HOST); |
876c9d3a MT |
813 | } |
814 | ||
18c52e7c DW |
815 | static void lbs_submit_command(struct lbs_private *priv, |
816 | struct cmd_ctrl_node *cmdnode) | |
876c9d3a MT |
817 | { |
818 | unsigned long flags; | |
ddac4526 | 819 | struct cmd_header *cmd; |
18c52e7c DW |
820 | uint16_t cmdsize; |
821 | uint16_t command; | |
57962f0b | 822 | int timeo = 3 * HZ; |
18c52e7c | 823 | int ret; |
876c9d3a | 824 | |
8ff12da1 | 825 | lbs_deb_enter(LBS_DEB_HOST); |
876c9d3a | 826 | |
ddac4526 | 827 | cmd = cmdnode->cmdbuf; |
876c9d3a | 828 | |
aa21c004 | 829 | spin_lock_irqsave(&priv->driver_lock, flags); |
aa21c004 DW |
830 | priv->cur_cmd = cmdnode; |
831 | priv->cur_cmd_retcode = 0; | |
832 | spin_unlock_irqrestore(&priv->driver_lock, flags); | |
876c9d3a | 833 | |
ddac4526 DW |
834 | cmdsize = le16_to_cpu(cmd->size); |
835 | command = le16_to_cpu(cmd->command); | |
876c9d3a | 836 | |
18c52e7c | 837 | /* These commands take longer */ |
be0d76e4 | 838 | if (command == CMD_802_11_SCAN || command == CMD_802_11_ASSOCIATE) |
57962f0b | 839 | timeo = 5 * HZ; |
18c52e7c | 840 | |
e5225b39 HS |
841 | lbs_deb_cmd("DNLD_CMD: command 0x%04x, seq %d, size %d\n", |
842 | command, le16_to_cpu(cmd->seqnum), cmdsize); | |
1afc09ab | 843 | lbs_deb_hex(LBS_DEB_CMD, "DNLD_CMD", (void *) cmdnode->cmdbuf, cmdsize); |
8ff12da1 | 844 | |
ddac4526 | 845 | ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) cmd, cmdsize); |
18c52e7c | 846 | |
d9896ee1 DW |
847 | if (ret) { |
848 | lbs_pr_info("DNLD_CMD: hw_host_to_card failed: %d\n", ret); | |
18c52e7c DW |
849 | /* Let the timer kick in and retry, and potentially reset |
850 | the whole thing if the condition persists */ | |
57962f0b | 851 | timeo = HZ/4; |
1afc09ab | 852 | } |
876c9d3a | 853 | |
49125454 AK |
854 | if (command == CMD_802_11_DEEP_SLEEP) { |
855 | if (priv->is_auto_deep_sleep_enabled) { | |
856 | priv->wakeup_dev_required = 1; | |
857 | priv->dnld_sent = 0; | |
858 | } | |
859 | priv->is_deep_sleep = 1; | |
860 | lbs_complete_command(priv, cmdnode, 0); | |
861 | } else { | |
862 | /* Setup the timer after transmit command */ | |
863 | mod_timer(&priv->command_timer, jiffies + timeo); | |
864 | } | |
876c9d3a | 865 | |
18c52e7c | 866 | lbs_deb_leave(LBS_DEB_HOST); |
876c9d3a MT |
867 | } |
868 | ||
876c9d3a MT |
869 | /** |
870 | * This function inserts command node to cmdfreeq | |
aa21c004 | 871 | * after cleans it. Requires priv->driver_lock held. |
876c9d3a | 872 | */ |
183aeac1 | 873 | static void __lbs_cleanup_and_insert_cmd(struct lbs_private *priv, |
5ba2f8a0 | 874 | struct cmd_ctrl_node *cmdnode) |
876c9d3a | 875 | { |
5ba2f8a0 DW |
876 | lbs_deb_enter(LBS_DEB_HOST); |
877 | ||
878 | if (!cmdnode) | |
879 | goto out; | |
880 | ||
5ba2f8a0 DW |
881 | cmdnode->callback = NULL; |
882 | cmdnode->callback_arg = 0; | |
876c9d3a | 883 | |
5ba2f8a0 | 884 | memset(cmdnode->cmdbuf, 0, LBS_CMD_BUFFER_SIZE); |
876c9d3a | 885 | |
5ba2f8a0 DW |
886 | list_add_tail(&cmdnode->list, &priv->cmdfreeq); |
887 | out: | |
888 | lbs_deb_leave(LBS_DEB_HOST); | |
876c9d3a MT |
889 | } |
890 | ||
69f9032d HS |
891 | static void lbs_cleanup_and_insert_cmd(struct lbs_private *priv, |
892 | struct cmd_ctrl_node *ptempcmd) | |
876c9d3a MT |
893 | { |
894 | unsigned long flags; | |
895 | ||
aa21c004 | 896 | spin_lock_irqsave(&priv->driver_lock, flags); |
10078321 | 897 | __lbs_cleanup_and_insert_cmd(priv, ptempcmd); |
aa21c004 | 898 | spin_unlock_irqrestore(&priv->driver_lock, flags); |
876c9d3a MT |
899 | } |
900 | ||
183aeac1 DW |
901 | void lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd, |
902 | int result) | |
903 | { | |
904 | if (cmd == priv->cur_cmd) | |
905 | priv->cur_cmd_retcode = result; | |
5ba2f8a0 | 906 | |
ae125bf8 | 907 | cmd->result = result; |
5ba2f8a0 DW |
908 | cmd->cmdwaitqwoken = 1; |
909 | wake_up_interruptible(&cmd->cmdwait_q); | |
910 | ||
8db4a2b9 | 911 | if (!cmd->callback || cmd->callback == lbs_cmd_async_callback) |
ad12d0f4 | 912 | __lbs_cleanup_and_insert_cmd(priv, cmd); |
183aeac1 DW |
913 | priv->cur_cmd = NULL; |
914 | } | |
915 | ||
d5db2dfa | 916 | int lbs_set_radio(struct lbs_private *priv, u8 preamble, u8 radio_on) |
876c9d3a | 917 | { |
a7c45890 | 918 | struct cmd_ds_802_11_radio_control cmd; |
d5db2dfa | 919 | int ret = -EINVAL; |
876c9d3a | 920 | |
9012b28a | 921 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 922 | |
a7c45890 DW |
923 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); |
924 | cmd.action = cpu_to_le16(CMD_ACT_SET); | |
925 | ||
d5db2dfa DW |
926 | /* Only v8 and below support setting the preamble */ |
927 | if (priv->fwrelease < 0x09000000) { | |
928 | switch (preamble) { | |
929 | case RADIO_PREAMBLE_SHORT: | |
d5db2dfa DW |
930 | case RADIO_PREAMBLE_AUTO: |
931 | case RADIO_PREAMBLE_LONG: | |
932 | cmd.control = cpu_to_le16(preamble); | |
933 | break; | |
934 | default: | |
935 | goto out; | |
936 | } | |
937 | } | |
a7c45890 | 938 | |
d5db2dfa DW |
939 | if (radio_on) |
940 | cmd.control |= cpu_to_le16(0x1); | |
941 | else { | |
942 | cmd.control &= cpu_to_le16(~0x1); | |
943 | priv->txpower_cur = 0; | |
a7c45890 | 944 | } |
876c9d3a | 945 | |
d5db2dfa DW |
946 | lbs_deb_cmd("RADIO_CONTROL: radio %s, preamble %d\n", |
947 | radio_on ? "ON" : "OFF", preamble); | |
a7c45890 | 948 | |
d5db2dfa | 949 | priv->radio_on = radio_on; |
a7c45890 DW |
950 | |
951 | ret = lbs_cmd_with_response(priv, CMD_802_11_RADIO_CONTROL, &cmd); | |
876c9d3a | 952 | |
d5db2dfa | 953 | out: |
9012b28a | 954 | lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); |
876c9d3a MT |
955 | return ret; |
956 | } | |
957 | ||
c97329e2 | 958 | void lbs_set_mac_control(struct lbs_private *priv) |
876c9d3a | 959 | { |
835d3ac5 | 960 | struct cmd_ds_mac_control cmd; |
876c9d3a | 961 | |
9012b28a | 962 | lbs_deb_enter(LBS_DEB_CMD); |
876c9d3a | 963 | |
835d3ac5 | 964 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); |
d9e9778c | 965 | cmd.action = cpu_to_le16(priv->mac_control); |
835d3ac5 HS |
966 | cmd.reserved = 0; |
967 | ||
75bf45a7 | 968 | lbs_cmd_async(priv, CMD_MAC_CONTROL, &cmd.hdr, sizeof(cmd)); |
876c9d3a | 969 | |
c97329e2 | 970 | lbs_deb_leave(LBS_DEB_CMD); |
876c9d3a MT |
971 | } |
972 | ||
1047d5ed KD |
973 | /** |
974 | * @brief This function implements command CMD_802_11D_DOMAIN_INFO | |
975 | * @param priv pointer to struct lbs_private | |
976 | * @param cmd pointer to cmd buffer | |
977 | * @param cmdno cmd ID | |
978 | * @param cmdOption cmd action | |
979 | * @return 0 | |
980 | */ | |
981 | int lbs_cmd_802_11d_domain_info(struct lbs_private *priv, | |
982 | struct cmd_ds_command *cmd, | |
983 | u16 cmdoption) | |
984 | { | |
985 | struct cmd_ds_802_11d_domain_info *pdomaininfo = | |
986 | &cmd->params.domaininfo; | |
987 | struct mrvl_ie_domain_param_set *domain = &pdomaininfo->domain; | |
988 | u8 nr_triplet = priv->domain_reg.no_triplet; | |
989 | ||
990 | lbs_deb_enter(LBS_DEB_11D); | |
991 | ||
992 | lbs_deb_11d("nr_triplet=%x\n", nr_triplet); | |
993 | ||
994 | pdomaininfo->action = cpu_to_le16(cmdoption); | |
995 | if (cmdoption == CMD_ACT_GET) { | |
996 | cmd->size = cpu_to_le16(sizeof(pdomaininfo->action) + | |
997 | sizeof(struct cmd_header)); | |
998 | lbs_deb_hex(LBS_DEB_11D, "802_11D_DOMAIN_INFO", (u8 *) cmd, | |
999 | le16_to_cpu(cmd->size)); | |
1000 | goto done; | |
1001 | } | |
1002 | ||
1003 | domain->header.type = cpu_to_le16(TLV_TYPE_DOMAIN); | |
1004 | memcpy(domain->countrycode, priv->domain_reg.country_code, | |
1005 | sizeof(domain->countrycode)); | |
1006 | ||
1007 | domain->header.len = cpu_to_le16(nr_triplet | |
1008 | * sizeof(struct ieee80211_country_ie_triplet) | |
1009 | + sizeof(domain->countrycode)); | |
1010 | ||
1011 | if (nr_triplet) { | |
1012 | memcpy(domain->triplet, priv->domain_reg.triplet, | |
1013 | nr_triplet * | |
1014 | sizeof(struct ieee80211_country_ie_triplet)); | |
1015 | ||
1016 | cmd->size = cpu_to_le16(sizeof(pdomaininfo->action) + | |
1017 | le16_to_cpu(domain->header.len) + | |
1018 | sizeof(struct mrvl_ie_header) + | |
1019 | sizeof(struct cmd_header)); | |
1020 | } else { | |
1021 | cmd->size = cpu_to_le16(sizeof(pdomaininfo->action) + | |
1022 | sizeof(struct cmd_header)); | |
1023 | } | |
1024 | ||
1025 | lbs_deb_hex(LBS_DEB_11D, "802_11D_DOMAIN_INFO", (u8 *) cmd, | |
1026 | le16_to_cpu(cmd->size)); | |
1027 | ||
1028 | done: | |
1029 | lbs_deb_enter(LBS_DEB_11D); | |
1030 | return 0; | |
1031 | } | |
1032 | ||
876c9d3a MT |
1033 | /** |
1034 | * @brief This function prepare the command before send to firmware. | |
1035 | * | |
69f9032d | 1036 | * @param priv A pointer to struct lbs_private structure |
876c9d3a MT |
1037 | * @param cmd_no command number |
1038 | * @param cmd_action command action: GET or SET | |
1039 | * @param wait_option wait option: wait response or not | |
1040 | * @param cmd_oid cmd oid: treated as sub command | |
1041 | * @param pdata_buf A pointer to informaion buffer | |
1042 | * @return 0 or -1 | |
1043 | */ | |
69f9032d | 1044 | int lbs_prepare_and_send_command(struct lbs_private *priv, |
876c9d3a MT |
1045 | u16 cmd_no, |
1046 | u16 cmd_action, | |
1047 | u16 wait_option, u32 cmd_oid, void *pdata_buf) | |
1048 | { | |
1049 | int ret = 0; | |
876c9d3a MT |
1050 | struct cmd_ctrl_node *cmdnode; |
1051 | struct cmd_ds_command *cmdptr; | |
1052 | unsigned long flags; | |
1053 | ||
8ff12da1 | 1054 | lbs_deb_enter(LBS_DEB_HOST); |
876c9d3a | 1055 | |
aa21c004 DW |
1056 | if (!priv) { |
1057 | lbs_deb_host("PREP_CMD: priv is NULL\n"); | |
876c9d3a MT |
1058 | ret = -1; |
1059 | goto done; | |
1060 | } | |
1061 | ||
aa21c004 | 1062 | if (priv->surpriseremoved) { |
8ff12da1 | 1063 | lbs_deb_host("PREP_CMD: card removed\n"); |
876c9d3a MT |
1064 | ret = -1; |
1065 | goto done; | |
1066 | } | |
1067 | ||
63f275df AK |
1068 | if (!lbs_is_cmd_allowed(priv)) { |
1069 | ret = -EBUSY; | |
1070 | goto done; | |
1071 | } | |
1072 | ||
0d61d042 | 1073 | cmdnode = lbs_get_cmd_ctrl_node(priv); |
876c9d3a MT |
1074 | |
1075 | if (cmdnode == NULL) { | |
8ff12da1 | 1076 | lbs_deb_host("PREP_CMD: cmdnode is NULL\n"); |
876c9d3a MT |
1077 | |
1078 | /* Wake up main thread to execute next command */ | |
fe336150 | 1079 | wake_up_interruptible(&priv->waitq); |
876c9d3a MT |
1080 | ret = -1; |
1081 | goto done; | |
1082 | } | |
1083 | ||
e98a88dd HS |
1084 | cmdnode->callback = NULL; |
1085 | cmdnode->callback_arg = (unsigned long)pdata_buf; | |
876c9d3a | 1086 | |
ddac4526 | 1087 | cmdptr = (struct cmd_ds_command *)cmdnode->cmdbuf; |
876c9d3a | 1088 | |
8ff12da1 | 1089 | lbs_deb_host("PREP_CMD: command 0x%04x\n", cmd_no); |
876c9d3a | 1090 | |
876c9d3a | 1091 | /* Set sequence number, command and INT option */ |
aa21c004 DW |
1092 | priv->seqnum++; |
1093 | cmdptr->seqnum = cpu_to_le16(priv->seqnum); | |
876c9d3a | 1094 | |
981f187b | 1095 | cmdptr->command = cpu_to_le16(cmd_no); |
876c9d3a MT |
1096 | cmdptr->result = 0; |
1097 | ||
1098 | switch (cmd_no) { | |
0aef64d7 | 1099 | case CMD_802_11_PS_MODE: |
e98a88dd | 1100 | ret = lbs_cmd_802_11_ps_mode(cmdptr, cmd_action); |
876c9d3a MT |
1101 | break; |
1102 | ||
0aef64d7 DW |
1103 | case CMD_MAC_REG_ACCESS: |
1104 | case CMD_BBP_REG_ACCESS: | |
1105 | case CMD_RF_REG_ACCESS: | |
e98a88dd | 1106 | ret = lbs_cmd_reg_access(cmdptr, cmd_action, pdata_buf); |
876c9d3a MT |
1107 | break; |
1108 | ||
0aef64d7 | 1109 | case CMD_802_11_RSSI: |
10078321 | 1110 | ret = lbs_cmd_802_11_rssi(priv, cmdptr); |
876c9d3a MT |
1111 | break; |
1112 | ||
0aef64d7 DW |
1113 | case CMD_802_11_SET_AFC: |
1114 | case CMD_802_11_GET_AFC: | |
876c9d3a MT |
1115 | |
1116 | cmdptr->command = cpu_to_le16(cmd_no); | |
981f187b | 1117 | cmdptr->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_afc) + |
8ec97cc8 | 1118 | sizeof(struct cmd_header)); |
876c9d3a MT |
1119 | |
1120 | memmove(&cmdptr->params.afc, | |
1121 | pdata_buf, sizeof(struct cmd_ds_802_11_afc)); | |
1122 | ||
1123 | ret = 0; | |
1124 | goto done; | |
1125 | ||
1047d5ed KD |
1126 | case CMD_802_11D_DOMAIN_INFO: |
1127 | cmdptr->command = cpu_to_le16(cmd_no); | |
1128 | ret = lbs_cmd_802_11d_domain_info(priv, cmdptr, cmd_action); | |
1129 | break; | |
1130 | ||
0aef64d7 DW |
1131 | case CMD_802_11_TPC_CFG: |
1132 | cmdptr->command = cpu_to_le16(CMD_802_11_TPC_CFG); | |
876c9d3a MT |
1133 | cmdptr->size = |
1134 | cpu_to_le16(sizeof(struct cmd_ds_802_11_tpc_cfg) + | |
8ec97cc8 | 1135 | sizeof(struct cmd_header)); |
876c9d3a MT |
1136 | |
1137 | memmove(&cmdptr->params.tpccfg, | |
1138 | pdata_buf, sizeof(struct cmd_ds_802_11_tpc_cfg)); | |
1139 | ||
1140 | ret = 0; | |
1141 | break; | |
5844d12e | 1142 | |
4143a23d HS |
1143 | #ifdef CONFIG_LIBERTAS_MESH |
1144 | ||
0aef64d7 | 1145 | case CMD_BT_ACCESS: |
e98a88dd | 1146 | ret = lbs_cmd_bt_access(cmdptr, cmd_action, pdata_buf); |
876c9d3a MT |
1147 | break; |
1148 | ||
0aef64d7 | 1149 | case CMD_FWT_ACCESS: |
e98a88dd | 1150 | ret = lbs_cmd_fwt_access(cmdptr, cmd_action, pdata_buf); |
876c9d3a MT |
1151 | break; |
1152 | ||
4143a23d HS |
1153 | #endif |
1154 | ||
96287ac4 BD |
1155 | case CMD_802_11_BEACON_CTRL: |
1156 | ret = lbs_cmd_bcn_ctrl(priv, cmdptr, cmd_action); | |
1157 | break; | |
49125454 AK |
1158 | case CMD_802_11_DEEP_SLEEP: |
1159 | cmdptr->command = cpu_to_le16(CMD_802_11_DEEP_SLEEP); | |
8ec97cc8 | 1160 | cmdptr->size = cpu_to_le16(sizeof(struct cmd_header)); |
49125454 | 1161 | break; |
876c9d3a | 1162 | default: |
e37fc6e1 | 1163 | lbs_pr_err("PREP_CMD: unknown command 0x%04x\n", cmd_no); |
876c9d3a MT |
1164 | ret = -1; |
1165 | break; | |
1166 | } | |
1167 | ||
1168 | /* return error, since the command preparation failed */ | |
1169 | if (ret != 0) { | |
8ff12da1 | 1170 | lbs_deb_host("PREP_CMD: command preparation failed\n"); |
10078321 | 1171 | lbs_cleanup_and_insert_cmd(priv, cmdnode); |
876c9d3a MT |
1172 | ret = -1; |
1173 | goto done; | |
1174 | } | |
1175 | ||
1176 | cmdnode->cmdwaitqwoken = 0; | |
1177 | ||
681ffbb7 | 1178 | lbs_queue_cmd(priv, cmdnode); |
fe336150 | 1179 | wake_up_interruptible(&priv->waitq); |
876c9d3a | 1180 | |
0aef64d7 | 1181 | if (wait_option & CMD_OPTION_WAITFORRSP) { |
8ff12da1 | 1182 | lbs_deb_host("PREP_CMD: wait for response\n"); |
876c9d3a MT |
1183 | might_sleep(); |
1184 | wait_event_interruptible(cmdnode->cmdwait_q, | |
1185 | cmdnode->cmdwaitqwoken); | |
1186 | } | |
1187 | ||
aa21c004 DW |
1188 | spin_lock_irqsave(&priv->driver_lock, flags); |
1189 | if (priv->cur_cmd_retcode) { | |
8ff12da1 | 1190 | lbs_deb_host("PREP_CMD: command failed with return code %d\n", |
aa21c004 DW |
1191 | priv->cur_cmd_retcode); |
1192 | priv->cur_cmd_retcode = 0; | |
876c9d3a MT |
1193 | ret = -1; |
1194 | } | |
aa21c004 | 1195 | spin_unlock_irqrestore(&priv->driver_lock, flags); |
876c9d3a MT |
1196 | |
1197 | done: | |
8ff12da1 | 1198 | lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); |
876c9d3a MT |
1199 | return ret; |
1200 | } | |
1201 | ||
1202 | /** | |
1203 | * @brief This function allocates the command buffer and link | |
1204 | * it to command free queue. | |
1205 | * | |
69f9032d | 1206 | * @param priv A pointer to struct lbs_private structure |
876c9d3a MT |
1207 | * @return 0 or -1 |
1208 | */ | |
69f9032d | 1209 | int lbs_allocate_cmd_buffer(struct lbs_private *priv) |
876c9d3a MT |
1210 | { |
1211 | int ret = 0; | |
ddac4526 | 1212 | u32 bufsize; |
876c9d3a | 1213 | u32 i; |
ddac4526 | 1214 | struct cmd_ctrl_node *cmdarray; |
876c9d3a | 1215 | |
8ff12da1 | 1216 | lbs_deb_enter(LBS_DEB_HOST); |
876c9d3a | 1217 | |
ddac4526 DW |
1218 | /* Allocate and initialize the command array */ |
1219 | bufsize = sizeof(struct cmd_ctrl_node) * LBS_NUM_CMD_BUFFERS; | |
1220 | if (!(cmdarray = kzalloc(bufsize, GFP_KERNEL))) { | |
8ff12da1 | 1221 | lbs_deb_host("ALLOC_CMD_BUF: tempcmd_array is NULL\n"); |
876c9d3a MT |
1222 | ret = -1; |
1223 | goto done; | |
1224 | } | |
ddac4526 | 1225 | priv->cmd_array = cmdarray; |
876c9d3a | 1226 | |
ddac4526 DW |
1227 | /* Allocate and initialize each command buffer in the command array */ |
1228 | for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { | |
1229 | cmdarray[i].cmdbuf = kzalloc(LBS_CMD_BUFFER_SIZE, GFP_KERNEL); | |
1230 | if (!cmdarray[i].cmdbuf) { | |
8ff12da1 | 1231 | lbs_deb_host("ALLOC_CMD_BUF: ptempvirtualaddr is NULL\n"); |
876c9d3a MT |
1232 | ret = -1; |
1233 | goto done; | |
1234 | } | |
876c9d3a MT |
1235 | } |
1236 | ||
ddac4526 DW |
1237 | for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { |
1238 | init_waitqueue_head(&cmdarray[i].cmdwait_q); | |
1239 | lbs_cleanup_and_insert_cmd(priv, &cmdarray[i]); | |
876c9d3a | 1240 | } |
876c9d3a | 1241 | ret = 0; |
9012b28a HS |
1242 | |
1243 | done: | |
8ff12da1 | 1244 | lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); |
876c9d3a MT |
1245 | return ret; |
1246 | } | |
1247 | ||
1248 | /** | |
1249 | * @brief This function frees the command buffer. | |
1250 | * | |
69f9032d | 1251 | * @param priv A pointer to struct lbs_private structure |
876c9d3a MT |
1252 | * @return 0 or -1 |
1253 | */ | |
69f9032d | 1254 | int lbs_free_cmd_buffer(struct lbs_private *priv) |
876c9d3a | 1255 | { |
ddac4526 | 1256 | struct cmd_ctrl_node *cmdarray; |
876c9d3a | 1257 | unsigned int i; |
876c9d3a | 1258 | |
8ff12da1 | 1259 | lbs_deb_enter(LBS_DEB_HOST); |
876c9d3a MT |
1260 | |
1261 | /* need to check if cmd array is allocated or not */ | |
aa21c004 | 1262 | if (priv->cmd_array == NULL) { |
8ff12da1 | 1263 | lbs_deb_host("FREE_CMD_BUF: cmd_array is NULL\n"); |
876c9d3a MT |
1264 | goto done; |
1265 | } | |
1266 | ||
ddac4526 | 1267 | cmdarray = priv->cmd_array; |
876c9d3a MT |
1268 | |
1269 | /* Release shared memory buffers */ | |
ddac4526 DW |
1270 | for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { |
1271 | if (cmdarray[i].cmdbuf) { | |
1272 | kfree(cmdarray[i].cmdbuf); | |
1273 | cmdarray[i].cmdbuf = NULL; | |
876c9d3a MT |
1274 | } |
1275 | } | |
1276 | ||
1277 | /* Release cmd_ctrl_node */ | |
aa21c004 DW |
1278 | if (priv->cmd_array) { |
1279 | kfree(priv->cmd_array); | |
1280 | priv->cmd_array = NULL; | |
876c9d3a MT |
1281 | } |
1282 | ||
1283 | done: | |
8ff12da1 | 1284 | lbs_deb_leave(LBS_DEB_HOST); |
876c9d3a MT |
1285 | return 0; |
1286 | } | |
1287 | ||
1288 | /** | |
1289 | * @brief This function gets a free command node if available in | |
1290 | * command free queue. | |
1291 | * | |
69f9032d | 1292 | * @param priv A pointer to struct lbs_private structure |
876c9d3a MT |
1293 | * @return cmd_ctrl_node A pointer to cmd_ctrl_node structure or NULL |
1294 | */ | |
2fd6cfe3 | 1295 | static struct cmd_ctrl_node *lbs_get_cmd_ctrl_node(struct lbs_private *priv) |
876c9d3a MT |
1296 | { |
1297 | struct cmd_ctrl_node *tempnode; | |
876c9d3a MT |
1298 | unsigned long flags; |
1299 | ||
8ff12da1 HS |
1300 | lbs_deb_enter(LBS_DEB_HOST); |
1301 | ||
aa21c004 | 1302 | if (!priv) |
876c9d3a MT |
1303 | return NULL; |
1304 | ||
aa21c004 | 1305 | spin_lock_irqsave(&priv->driver_lock, flags); |
876c9d3a | 1306 | |
aa21c004 DW |
1307 | if (!list_empty(&priv->cmdfreeq)) { |
1308 | tempnode = list_first_entry(&priv->cmdfreeq, | |
abe3ed14 LZ |
1309 | struct cmd_ctrl_node, list); |
1310 | list_del(&tempnode->list); | |
876c9d3a | 1311 | } else { |
8ff12da1 | 1312 | lbs_deb_host("GET_CMD_NODE: cmd_ctrl_node is not available\n"); |
876c9d3a MT |
1313 | tempnode = NULL; |
1314 | } | |
1315 | ||
aa21c004 | 1316 | spin_unlock_irqrestore(&priv->driver_lock, flags); |
876c9d3a | 1317 | |
8ff12da1 | 1318 | lbs_deb_leave(LBS_DEB_HOST); |
876c9d3a MT |
1319 | return tempnode; |
1320 | } | |
1321 | ||
876c9d3a MT |
1322 | /** |
1323 | * @brief This function executes next command in command | |
877d0310 | 1324 | * pending queue. It will put firmware back to PS mode |
876c9d3a MT |
1325 | * if applicable. |
1326 | * | |
69f9032d | 1327 | * @param priv A pointer to struct lbs_private structure |
876c9d3a MT |
1328 | * @return 0 or -1 |
1329 | */ | |
69f9032d | 1330 | int lbs_execute_next_command(struct lbs_private *priv) |
876c9d3a | 1331 | { |
876c9d3a | 1332 | struct cmd_ctrl_node *cmdnode = NULL; |
ddac4526 | 1333 | struct cmd_header *cmd; |
876c9d3a MT |
1334 | unsigned long flags; |
1335 | int ret = 0; | |
1336 | ||
1afc09ab HS |
1337 | /* Debug group is LBS_DEB_THREAD and not LBS_DEB_HOST, because the |
1338 | * only caller to us is lbs_thread() and we get even when a | |
1339 | * data packet is received */ | |
8ff12da1 | 1340 | lbs_deb_enter(LBS_DEB_THREAD); |
876c9d3a | 1341 | |
aa21c004 | 1342 | spin_lock_irqsave(&priv->driver_lock, flags); |
876c9d3a | 1343 | |
aa21c004 | 1344 | if (priv->cur_cmd) { |
8ff12da1 | 1345 | lbs_pr_alert( "EXEC_NEXT_CMD: already processing command!\n"); |
aa21c004 | 1346 | spin_unlock_irqrestore(&priv->driver_lock, flags); |
876c9d3a MT |
1347 | ret = -1; |
1348 | goto done; | |
1349 | } | |
1350 | ||
aa21c004 DW |
1351 | if (!list_empty(&priv->cmdpendingq)) { |
1352 | cmdnode = list_first_entry(&priv->cmdpendingq, | |
abe3ed14 | 1353 | struct cmd_ctrl_node, list); |
876c9d3a MT |
1354 | } |
1355 | ||
aa21c004 | 1356 | spin_unlock_irqrestore(&priv->driver_lock, flags); |
876c9d3a MT |
1357 | |
1358 | if (cmdnode) { | |
ddac4526 | 1359 | cmd = cmdnode->cmdbuf; |
876c9d3a | 1360 | |
ddac4526 | 1361 | if (is_command_allowed_in_ps(le16_to_cpu(cmd->command))) { |
aa21c004 DW |
1362 | if ((priv->psstate == PS_STATE_SLEEP) || |
1363 | (priv->psstate == PS_STATE_PRE_SLEEP)) { | |
8ff12da1 HS |
1364 | lbs_deb_host( |
1365 | "EXEC_NEXT_CMD: cannot send cmd 0x%04x in psstate %d\n", | |
ddac4526 | 1366 | le16_to_cpu(cmd->command), |
aa21c004 | 1367 | priv->psstate); |
876c9d3a MT |
1368 | ret = -1; |
1369 | goto done; | |
1370 | } | |
8ff12da1 | 1371 | lbs_deb_host("EXEC_NEXT_CMD: OK to send command " |
ddac4526 DW |
1372 | "0x%04x in psstate %d\n", |
1373 | le16_to_cpu(cmd->command), priv->psstate); | |
aa21c004 | 1374 | } else if (priv->psstate != PS_STATE_FULL_POWER) { |
876c9d3a MT |
1375 | /* |
1376 | * 1. Non-PS command: | |
1377 | * Queue it. set needtowakeup to TRUE if current state | |
10078321 | 1378 | * is SLEEP, otherwise call lbs_ps_wakeup to send Exit_PS. |
876c9d3a MT |
1379 | * 2. PS command but not Exit_PS: |
1380 | * Ignore it. | |
1381 | * 3. PS command Exit_PS: | |
1382 | * Set needtowakeup to TRUE if current state is SLEEP, | |
1383 | * otherwise send this command down to firmware | |
1384 | * immediately. | |
1385 | */ | |
ddac4526 | 1386 | if (cmd->command != cpu_to_le16(CMD_802_11_PS_MODE)) { |
876c9d3a MT |
1387 | /* Prepare to send Exit PS, |
1388 | * this non PS command will be sent later */ | |
aa21c004 DW |
1389 | if ((priv->psstate == PS_STATE_SLEEP) |
1390 | || (priv->psstate == PS_STATE_PRE_SLEEP) | |
876c9d3a MT |
1391 | ) { |
1392 | /* w/ new scheme, it will not reach here. | |
1393 | since it is blocked in main_thread. */ | |
aa21c004 | 1394 | priv->needtowakeup = 1; |
876c9d3a | 1395 | } else |
10078321 | 1396 | lbs_ps_wakeup(priv, 0); |
876c9d3a MT |
1397 | |
1398 | ret = 0; | |
1399 | goto done; | |
1400 | } else { | |
1401 | /* | |
1402 | * PS command. Ignore it if it is not Exit_PS. | |
1403 | * otherwise send it down immediately. | |
1404 | */ | |
38bfab1a | 1405 | struct cmd_ds_802_11_ps_mode *psm = (void *)&cmd[1]; |
876c9d3a | 1406 | |
8ff12da1 HS |
1407 | lbs_deb_host( |
1408 | "EXEC_NEXT_CMD: PS cmd, action 0x%02x\n", | |
876c9d3a MT |
1409 | psm->action); |
1410 | if (psm->action != | |
0aef64d7 | 1411 | cpu_to_le16(CMD_SUBCMD_EXIT_PS)) { |
8ff12da1 HS |
1412 | lbs_deb_host( |
1413 | "EXEC_NEXT_CMD: ignore ENTER_PS cmd\n"); | |
abe3ed14 | 1414 | list_del(&cmdnode->list); |
183aeac1 DW |
1415 | spin_lock_irqsave(&priv->driver_lock, flags); |
1416 | lbs_complete_command(priv, cmdnode, 0); | |
1417 | spin_unlock_irqrestore(&priv->driver_lock, flags); | |
876c9d3a MT |
1418 | |
1419 | ret = 0; | |
1420 | goto done; | |
1421 | } | |
1422 | ||
aa21c004 DW |
1423 | if ((priv->psstate == PS_STATE_SLEEP) || |
1424 | (priv->psstate == PS_STATE_PRE_SLEEP)) { | |
8ff12da1 HS |
1425 | lbs_deb_host( |
1426 | "EXEC_NEXT_CMD: ignore EXIT_PS cmd in sleep\n"); | |
abe3ed14 | 1427 | list_del(&cmdnode->list); |
183aeac1 DW |
1428 | spin_lock_irqsave(&priv->driver_lock, flags); |
1429 | lbs_complete_command(priv, cmdnode, 0); | |
1430 | spin_unlock_irqrestore(&priv->driver_lock, flags); | |
aa21c004 | 1431 | priv->needtowakeup = 1; |
876c9d3a MT |
1432 | |
1433 | ret = 0; | |
1434 | goto done; | |
1435 | } | |
1436 | ||
8ff12da1 HS |
1437 | lbs_deb_host( |
1438 | "EXEC_NEXT_CMD: sending EXIT_PS\n"); | |
876c9d3a MT |
1439 | } |
1440 | } | |
abe3ed14 | 1441 | list_del(&cmdnode->list); |
8ff12da1 | 1442 | lbs_deb_host("EXEC_NEXT_CMD: sending command 0x%04x\n", |
ddac4526 | 1443 | le16_to_cpu(cmd->command)); |
d9896ee1 | 1444 | lbs_submit_command(priv, cmdnode); |
876c9d3a MT |
1445 | } else { |
1446 | /* | |
1447 | * check if in power save mode, if yes, put the device back | |
1448 | * to PS mode | |
1449 | */ | |
e86dc1ca KD |
1450 | #ifdef TODO |
1451 | /* | |
1452 | * This was the old code for libertas+wext. Someone that | |
1453 | * understands this beast should re-code it in a sane way. | |
1454 | * | |
1455 | * I actually don't understand why this is related to WPA | |
1456 | * and to connection status, shouldn't powering should be | |
1457 | * independ of such things? | |
1458 | */ | |
aa21c004 DW |
1459 | if ((priv->psmode != LBS802_11POWERMODECAM) && |
1460 | (priv->psstate == PS_STATE_FULL_POWER) && | |
1461 | ((priv->connect_status == LBS_CONNECTED) || | |
602114ae | 1462 | lbs_mesh_connected(priv))) { |
aa21c004 DW |
1463 | if (priv->secinfo.WPAenabled || |
1464 | priv->secinfo.WPA2enabled) { | |
876c9d3a | 1465 | /* check for valid WPA group keys */ |
aa21c004 DW |
1466 | if (priv->wpa_mcast_key.len || |
1467 | priv->wpa_unicast_key.len) { | |
8ff12da1 | 1468 | lbs_deb_host( |
876c9d3a MT |
1469 | "EXEC_NEXT_CMD: WPA enabled and GTK_SET" |
1470 | " go back to PS_SLEEP"); | |
10078321 | 1471 | lbs_ps_sleep(priv, 0); |
876c9d3a MT |
1472 | } |
1473 | } else { | |
8ff12da1 HS |
1474 | lbs_deb_host( |
1475 | "EXEC_NEXT_CMD: cmdpendingq empty, " | |
1476 | "go back to PS_SLEEP"); | |
10078321 | 1477 | lbs_ps_sleep(priv, 0); |
876c9d3a MT |
1478 | } |
1479 | } | |
e86dc1ca | 1480 | #endif |
876c9d3a MT |
1481 | } |
1482 | ||
1483 | ret = 0; | |
1484 | done: | |
8ff12da1 | 1485 | lbs_deb_leave(LBS_DEB_THREAD); |
876c9d3a MT |
1486 | return ret; |
1487 | } | |
1488 | ||
f539f2ef | 1489 | static void lbs_send_confirmsleep(struct lbs_private *priv) |
876c9d3a MT |
1490 | { |
1491 | unsigned long flags; | |
f539f2ef | 1492 | int ret; |
876c9d3a | 1493 | |
8ff12da1 | 1494 | lbs_deb_enter(LBS_DEB_HOST); |
f539f2ef HS |
1495 | lbs_deb_hex(LBS_DEB_HOST, "sleep confirm", (u8 *) &confirm_sleep, |
1496 | sizeof(confirm_sleep)); | |
876c9d3a | 1497 | |
f539f2ef HS |
1498 | ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) &confirm_sleep, |
1499 | sizeof(confirm_sleep)); | |
876c9d3a | 1500 | if (ret) { |
f539f2ef | 1501 | lbs_pr_alert("confirm_sleep failed\n"); |
7919b89c | 1502 | goto out; |
876c9d3a | 1503 | } |
7919b89c HS |
1504 | |
1505 | spin_lock_irqsave(&priv->driver_lock, flags); | |
1506 | ||
a01f5450 HS |
1507 | /* We don't get a response on the sleep-confirmation */ |
1508 | priv->dnld_sent = DNLD_RES_RECEIVED; | |
1509 | ||
66fceb69 AK |
1510 | if (priv->is_host_sleep_configured) { |
1511 | priv->is_host_sleep_activated = 1; | |
1512 | wake_up_interruptible(&priv->host_sleep_q); | |
1513 | } | |
1514 | ||
7919b89c | 1515 | /* If nothing to do, go back to sleep (?) */ |
e64c026d | 1516 | if (!kfifo_len(&priv->event_fifo) && !priv->resp_len[priv->resp_idx]) |
7919b89c HS |
1517 | priv->psstate = PS_STATE_SLEEP; |
1518 | ||
1519 | spin_unlock_irqrestore(&priv->driver_lock, flags); | |
1520 | ||
1521 | out: | |
f539f2ef | 1522 | lbs_deb_leave(LBS_DEB_HOST); |
876c9d3a MT |
1523 | } |
1524 | ||
69f9032d | 1525 | void lbs_ps_sleep(struct lbs_private *priv, int wait_option) |
876c9d3a | 1526 | { |
8ff12da1 | 1527 | lbs_deb_enter(LBS_DEB_HOST); |
876c9d3a MT |
1528 | |
1529 | /* | |
1530 | * PS is currently supported only in Infrastructure mode | |
1531 | * Remove this check if it is to be supported in IBSS mode also | |
1532 | */ | |
1533 | ||
10078321 | 1534 | lbs_prepare_and_send_command(priv, CMD_802_11_PS_MODE, |
0aef64d7 | 1535 | CMD_SUBCMD_ENTER_PS, wait_option, 0, NULL); |
876c9d3a | 1536 | |
8ff12da1 | 1537 | lbs_deb_leave(LBS_DEB_HOST); |
876c9d3a MT |
1538 | } |
1539 | ||
1540 | /** | |
8ff12da1 | 1541 | * @brief This function sends Exit_PS command to firmware. |
876c9d3a | 1542 | * |
69f9032d | 1543 | * @param priv A pointer to struct lbs_private structure |
876c9d3a MT |
1544 | * @param wait_option wait response or not |
1545 | * @return n/a | |
1546 | */ | |
69f9032d | 1547 | void lbs_ps_wakeup(struct lbs_private *priv, int wait_option) |
876c9d3a | 1548 | { |
981f187b | 1549 | __le32 Localpsmode; |
876c9d3a | 1550 | |
8ff12da1 | 1551 | lbs_deb_enter(LBS_DEB_HOST); |
876c9d3a | 1552 | |
10078321 | 1553 | Localpsmode = cpu_to_le32(LBS802_11POWERMODECAM); |
876c9d3a | 1554 | |
10078321 | 1555 | lbs_prepare_and_send_command(priv, CMD_802_11_PS_MODE, |
0aef64d7 | 1556 | CMD_SUBCMD_EXIT_PS, |
876c9d3a MT |
1557 | wait_option, 0, &Localpsmode); |
1558 | ||
8ff12da1 | 1559 | lbs_deb_leave(LBS_DEB_HOST); |
876c9d3a MT |
1560 | } |
1561 | ||
1562 | /** | |
1563 | * @brief This function checks condition and prepares to | |
1564 | * send sleep confirm command to firmware if ok. | |
1565 | * | |
69f9032d | 1566 | * @param priv A pointer to struct lbs_private structure |
876c9d3a MT |
1567 | * @param psmode Power Saving mode |
1568 | * @return n/a | |
1569 | */ | |
d4ff0ef6 | 1570 | void lbs_ps_confirm_sleep(struct lbs_private *priv) |
876c9d3a MT |
1571 | { |
1572 | unsigned long flags =0; | |
d4ff0ef6 | 1573 | int allowed = 1; |
876c9d3a | 1574 | |
8ff12da1 | 1575 | lbs_deb_enter(LBS_DEB_HOST); |
876c9d3a | 1576 | |
a01f5450 | 1577 | spin_lock_irqsave(&priv->driver_lock, flags); |
634b8f49 | 1578 | if (priv->dnld_sent) { |
876c9d3a | 1579 | allowed = 0; |
23d36eec | 1580 | lbs_deb_host("dnld_sent was set\n"); |
876c9d3a MT |
1581 | } |
1582 | ||
7919b89c | 1583 | /* In-progress command? */ |
aa21c004 | 1584 | if (priv->cur_cmd) { |
876c9d3a | 1585 | allowed = 0; |
23d36eec | 1586 | lbs_deb_host("cur_cmd was set\n"); |
876c9d3a | 1587 | } |
7919b89c HS |
1588 | |
1589 | /* Pending events or command responses? */ | |
e64c026d | 1590 | if (kfifo_len(&priv->event_fifo) || priv->resp_len[priv->resp_idx]) { |
876c9d3a | 1591 | allowed = 0; |
7919b89c | 1592 | lbs_deb_host("pending events or command responses\n"); |
876c9d3a | 1593 | } |
aa21c004 | 1594 | spin_unlock_irqrestore(&priv->driver_lock, flags); |
876c9d3a MT |
1595 | |
1596 | if (allowed) { | |
10078321 | 1597 | lbs_deb_host("sending lbs_ps_confirm_sleep\n"); |
f539f2ef | 1598 | lbs_send_confirmsleep(priv); |
876c9d3a | 1599 | } else { |
8ff12da1 | 1600 | lbs_deb_host("sleep confirm has been delayed\n"); |
876c9d3a MT |
1601 | } |
1602 | ||
8ff12da1 | 1603 | lbs_deb_leave(LBS_DEB_HOST); |
876c9d3a | 1604 | } |
675787e2 HS |
1605 | |
1606 | ||
0112c9e9 AN |
1607 | /** |
1608 | * @brief Configures the transmission power control functionality. | |
1609 | * | |
1610 | * @param priv A pointer to struct lbs_private structure | |
1611 | * @param enable Transmission power control enable | |
1612 | * @param p0 Power level when link quality is good (dBm). | |
1613 | * @param p1 Power level when link quality is fair (dBm). | |
1614 | * @param p2 Power level when link quality is poor (dBm). | |
1615 | * @param usesnr Use Signal to Noise Ratio in TPC | |
1616 | * | |
1617 | * @return 0 on success | |
1618 | */ | |
1619 | int lbs_set_tpc_cfg(struct lbs_private *priv, int enable, int8_t p0, int8_t p1, | |
1620 | int8_t p2, int usesnr) | |
1621 | { | |
1622 | struct cmd_ds_802_11_tpc_cfg cmd; | |
1623 | int ret; | |
1624 | ||
1625 | memset(&cmd, 0, sizeof(cmd)); | |
1626 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); | |
1627 | cmd.action = cpu_to_le16(CMD_ACT_SET); | |
1628 | cmd.enable = !!enable; | |
3ed6e080 | 1629 | cmd.usesnr = !!usesnr; |
0112c9e9 AN |
1630 | cmd.P0 = p0; |
1631 | cmd.P1 = p1; | |
1632 | cmd.P2 = p2; | |
1633 | ||
1634 | ret = lbs_cmd_with_response(priv, CMD_802_11_TPC_CFG, &cmd); | |
1635 | ||
1636 | return ret; | |
1637 | } | |
1638 | ||
1639 | /** | |
1640 | * @brief Configures the power adaptation settings. | |
1641 | * | |
1642 | * @param priv A pointer to struct lbs_private structure | |
1643 | * @param enable Power adaptation enable | |
1644 | * @param p0 Power level for 1, 2, 5.5 and 11 Mbps (dBm). | |
1645 | * @param p1 Power level for 6, 9, 12, 18, 22, 24 and 36 Mbps (dBm). | |
1646 | * @param p2 Power level for 48 and 54 Mbps (dBm). | |
1647 | * | |
1648 | * @return 0 on Success | |
1649 | */ | |
1650 | ||
1651 | int lbs_set_power_adapt_cfg(struct lbs_private *priv, int enable, int8_t p0, | |
1652 | int8_t p1, int8_t p2) | |
1653 | { | |
1654 | struct cmd_ds_802_11_pa_cfg cmd; | |
1655 | int ret; | |
1656 | ||
1657 | memset(&cmd, 0, sizeof(cmd)); | |
1658 | cmd.hdr.size = cpu_to_le16(sizeof(cmd)); | |
1659 | cmd.action = cpu_to_le16(CMD_ACT_SET); | |
1660 | cmd.enable = !!enable; | |
1661 | cmd.P0 = p0; | |
1662 | cmd.P1 = p1; | |
1663 | cmd.P2 = p2; | |
1664 | ||
1665 | ret = lbs_cmd_with_response(priv, CMD_802_11_PA_CFG , &cmd); | |
1666 | ||
1667 | return ret; | |
1668 | } | |
1669 | ||
1670 | ||
6d898b19 | 1671 | struct cmd_ctrl_node *__lbs_cmd_async(struct lbs_private *priv, |
8db4a2b9 HS |
1672 | uint16_t command, struct cmd_header *in_cmd, int in_cmd_size, |
1673 | int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *), | |
1674 | unsigned long callback_arg) | |
675787e2 | 1675 | { |
675787e2 | 1676 | struct cmd_ctrl_node *cmdnode; |
675787e2 HS |
1677 | |
1678 | lbs_deb_enter(LBS_DEB_HOST); | |
675787e2 | 1679 | |
aa21c004 | 1680 | if (priv->surpriseremoved) { |
675787e2 | 1681 | lbs_deb_host("PREP_CMD: card removed\n"); |
3399ea5f | 1682 | cmdnode = ERR_PTR(-ENOENT); |
675787e2 HS |
1683 | goto done; |
1684 | } | |
1685 | ||
63f275df AK |
1686 | if (!lbs_is_cmd_allowed(priv)) { |
1687 | cmdnode = ERR_PTR(-EBUSY); | |
1688 | goto done; | |
1689 | } | |
1690 | ||
675787e2 | 1691 | cmdnode = lbs_get_cmd_ctrl_node(priv); |
675787e2 HS |
1692 | if (cmdnode == NULL) { |
1693 | lbs_deb_host("PREP_CMD: cmdnode is NULL\n"); | |
1694 | ||
1695 | /* Wake up main thread to execute next command */ | |
1696 | wake_up_interruptible(&priv->waitq); | |
3399ea5f | 1697 | cmdnode = ERR_PTR(-ENOBUFS); |
675787e2 HS |
1698 | goto done; |
1699 | } | |
1700 | ||
448a51ae | 1701 | cmdnode->callback = callback; |
1309b55b | 1702 | cmdnode->callback_arg = callback_arg; |
675787e2 | 1703 | |
7ad994de | 1704 | /* Copy the incoming command to the buffer */ |
ddac4526 | 1705 | memcpy(cmdnode->cmdbuf, in_cmd, in_cmd_size); |
7ad994de | 1706 | |
675787e2 | 1707 | /* Set sequence number, clean result, move to buffer */ |
aa21c004 | 1708 | priv->seqnum++; |
ddac4526 DW |
1709 | cmdnode->cmdbuf->command = cpu_to_le16(command); |
1710 | cmdnode->cmdbuf->size = cpu_to_le16(in_cmd_size); | |
1711 | cmdnode->cmdbuf->seqnum = cpu_to_le16(priv->seqnum); | |
1712 | cmdnode->cmdbuf->result = 0; | |
675787e2 HS |
1713 | |
1714 | lbs_deb_host("PREP_CMD: command 0x%04x\n", command); | |
1715 | ||
675787e2 | 1716 | cmdnode->cmdwaitqwoken = 0; |
681ffbb7 | 1717 | lbs_queue_cmd(priv, cmdnode); |
675787e2 HS |
1718 | wake_up_interruptible(&priv->waitq); |
1719 | ||
3399ea5f DW |
1720 | done: |
1721 | lbs_deb_leave_args(LBS_DEB_HOST, "ret %p", cmdnode); | |
1722 | return cmdnode; | |
1723 | } | |
1724 | ||
8db4a2b9 HS |
1725 | void lbs_cmd_async(struct lbs_private *priv, uint16_t command, |
1726 | struct cmd_header *in_cmd, int in_cmd_size) | |
1727 | { | |
1728 | lbs_deb_enter(LBS_DEB_CMD); | |
1729 | __lbs_cmd_async(priv, command, in_cmd, in_cmd_size, | |
1730 | lbs_cmd_async_callback, 0); | |
1731 | lbs_deb_leave(LBS_DEB_CMD); | |
1732 | } | |
1733 | ||
3399ea5f DW |
1734 | int __lbs_cmd(struct lbs_private *priv, uint16_t command, |
1735 | struct cmd_header *in_cmd, int in_cmd_size, | |
1736 | int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *), | |
1737 | unsigned long callback_arg) | |
1738 | { | |
1739 | struct cmd_ctrl_node *cmdnode; | |
1740 | unsigned long flags; | |
1741 | int ret = 0; | |
1742 | ||
1743 | lbs_deb_enter(LBS_DEB_HOST); | |
1744 | ||
1745 | cmdnode = __lbs_cmd_async(priv, command, in_cmd, in_cmd_size, | |
1746 | callback, callback_arg); | |
1747 | if (IS_ERR(cmdnode)) { | |
1748 | ret = PTR_ERR(cmdnode); | |
1749 | goto done; | |
1750 | } | |
1751 | ||
675787e2 HS |
1752 | might_sleep(); |
1753 | wait_event_interruptible(cmdnode->cmdwait_q, cmdnode->cmdwaitqwoken); | |
1754 | ||
aa21c004 | 1755 | spin_lock_irqsave(&priv->driver_lock, flags); |
ae125bf8 DW |
1756 | ret = cmdnode->result; |
1757 | if (ret) | |
1758 | lbs_pr_info("PREP_CMD: command 0x%04x failed: %d\n", | |
1759 | command, ret); | |
3399ea5f | 1760 | |
ad12d0f4 | 1761 | __lbs_cleanup_and_insert_cmd(priv, cmdnode); |
aa21c004 | 1762 | spin_unlock_irqrestore(&priv->driver_lock, flags); |
675787e2 HS |
1763 | |
1764 | done: | |
1765 | lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); | |
1766 | return ret; | |
1767 | } | |
14e865ba | 1768 | EXPORT_SYMBOL_GPL(__lbs_cmd); |