diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/wireless/wl12xx/acx.c | 1 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/acx.h | 2 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/boot.c | 9 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/cmd.c | 84 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/cmd.h | 62 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/conf.h | 25 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/init.c | 19 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/io.h | 14 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/main.c | 207 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/rx.c | 8 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/rx.h | 12 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/wl12xx.h | 20 |
12 files changed, 459 insertions, 4 deletions
diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c index b5880eba06e..87caa94fd81 100644 --- a/drivers/net/wireless/wl12xx/acx.c +++ b/drivers/net/wireless/wl12xx/acx.c | |||
@@ -1067,6 +1067,7 @@ int wl1271_acx_sta_mem_cfg(struct wl1271 *wl) | |||
1067 | mem_conf->tx_free_req = mem->min_req_tx_blocks; | 1067 | mem_conf->tx_free_req = mem->min_req_tx_blocks; |
1068 | mem_conf->rx_free_req = mem->min_req_rx_blocks; | 1068 | mem_conf->rx_free_req = mem->min_req_rx_blocks; |
1069 | mem_conf->tx_min = mem->tx_min; | 1069 | mem_conf->tx_min = mem->tx_min; |
1070 | mem_conf->fwlog_blocks = wl->conf.fwlog.mem_blocks; | ||
1070 | 1071 | ||
1071 | ret = wl1271_cmd_configure(wl, ACX_MEM_CFG, mem_conf, | 1072 | ret = wl1271_cmd_configure(wl, ACX_MEM_CFG, mem_conf, |
1072 | sizeof(*mem_conf)); | 1073 | sizeof(*mem_conf)); |
diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h index f1d55313617..d303265f163 100644 --- a/drivers/net/wireless/wl12xx/acx.h +++ b/drivers/net/wireless/wl12xx/acx.h | |||
@@ -828,6 +828,8 @@ struct wl1271_acx_sta_config_memory { | |||
828 | u8 tx_free_req; | 828 | u8 tx_free_req; |
829 | u8 rx_free_req; | 829 | u8 rx_free_req; |
830 | u8 tx_min; | 830 | u8 tx_min; |
831 | u8 fwlog_blocks; | ||
832 | u8 padding[3]; | ||
831 | } __packed; | 833 | } __packed; |
832 | 834 | ||
833 | struct wl1271_acx_mem_map { | 835 | struct wl1271_acx_mem_map { |
diff --git a/drivers/net/wireless/wl12xx/boot.c b/drivers/net/wireless/wl12xx/boot.c index 2f0fb6a5bfd..101f7e0f632 100644 --- a/drivers/net/wireless/wl12xx/boot.c +++ b/drivers/net/wireless/wl12xx/boot.c | |||
@@ -117,6 +117,15 @@ static unsigned int wl12xx_get_fw_ver_quirks(struct wl1271 *wl) | |||
117 | (fw_ver[FW_VER_MINOR] < FW_VER_MINOR_1_SPARE_AP_MIN)))) | 117 | (fw_ver[FW_VER_MINOR] < FW_VER_MINOR_1_SPARE_AP_MIN)))) |
118 | quirks |= WL12XX_QUIRK_USE_2_SPARE_BLOCKS; | 118 | quirks |= WL12XX_QUIRK_USE_2_SPARE_BLOCKS; |
119 | 119 | ||
120 | /* Only new station firmwares support routing fw logs to the host */ | ||
121 | if ((fw_ver[FW_VER_IF_TYPE] == FW_VER_IF_TYPE_STA) && | ||
122 | (fw_ver[FW_VER_MINOR] < FW_VER_MINOR_FWLOG_STA_MIN)) | ||
123 | quirks |= WL12XX_QUIRK_FWLOG_NOT_IMPLEMENTED; | ||
124 | |||
125 | /* This feature is not yet supported for AP mode */ | ||
126 | if (fw_ver[FW_VER_IF_TYPE] == FW_VER_IF_TYPE_AP) | ||
127 | quirks |= WL12XX_QUIRK_FWLOG_NOT_IMPLEMENTED; | ||
128 | |||
120 | return quirks; | 129 | return quirks; |
121 | } | 130 | } |
122 | 131 | ||
diff --git a/drivers/net/wireless/wl12xx/cmd.c b/drivers/net/wireless/wl12xx/cmd.c index f3d332d11f8..c9a1fa52327 100644 --- a/drivers/net/wireless/wl12xx/cmd.c +++ b/drivers/net/wireless/wl12xx/cmd.c | |||
@@ -1234,3 +1234,87 @@ out_free: | |||
1234 | out: | 1234 | out: |
1235 | return ret; | 1235 | return ret; |
1236 | } | 1236 | } |
1237 | |||
1238 | int wl12xx_cmd_config_fwlog(struct wl1271 *wl) | ||
1239 | { | ||
1240 | struct wl12xx_cmd_config_fwlog *cmd; | ||
1241 | int ret = 0; | ||
1242 | |||
1243 | wl1271_debug(DEBUG_CMD, "cmd config firmware logger"); | ||
1244 | |||
1245 | cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); | ||
1246 | if (!cmd) { | ||
1247 | ret = -ENOMEM; | ||
1248 | goto out; | ||
1249 | } | ||
1250 | |||
1251 | cmd->logger_mode = wl->conf.fwlog.mode; | ||
1252 | cmd->log_severity = wl->conf.fwlog.severity; | ||
1253 | cmd->timestamp = wl->conf.fwlog.timestamp; | ||
1254 | cmd->output = wl->conf.fwlog.output; | ||
1255 | cmd->threshold = wl->conf.fwlog.threshold; | ||
1256 | |||
1257 | ret = wl1271_cmd_send(wl, CMD_CONFIG_FWLOGGER, cmd, sizeof(*cmd), 0); | ||
1258 | if (ret < 0) { | ||
1259 | wl1271_error("failed to send config firmware logger command"); | ||
1260 | goto out_free; | ||
1261 | } | ||
1262 | |||
1263 | out_free: | ||
1264 | kfree(cmd); | ||
1265 | |||
1266 | out: | ||
1267 | return ret; | ||
1268 | } | ||
1269 | |||
1270 | int wl12xx_cmd_start_fwlog(struct wl1271 *wl) | ||
1271 | { | ||
1272 | struct wl12xx_cmd_start_fwlog *cmd; | ||
1273 | int ret = 0; | ||
1274 | |||
1275 | wl1271_debug(DEBUG_CMD, "cmd start firmware logger"); | ||
1276 | |||
1277 | cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); | ||
1278 | if (!cmd) { | ||
1279 | ret = -ENOMEM; | ||
1280 | goto out; | ||
1281 | } | ||
1282 | |||
1283 | ret = wl1271_cmd_send(wl, CMD_START_FWLOGGER, cmd, sizeof(*cmd), 0); | ||
1284 | if (ret < 0) { | ||
1285 | wl1271_error("failed to send start firmware logger command"); | ||
1286 | goto out_free; | ||
1287 | } | ||
1288 | |||
1289 | out_free: | ||
1290 | kfree(cmd); | ||
1291 | |||
1292 | out: | ||
1293 | return ret; | ||
1294 | } | ||
1295 | |||
1296 | int wl12xx_cmd_stop_fwlog(struct wl1271 *wl) | ||
1297 | { | ||
1298 | struct wl12xx_cmd_stop_fwlog *cmd; | ||
1299 | int ret = 0; | ||
1300 | |||
1301 | wl1271_debug(DEBUG_CMD, "cmd stop firmware logger"); | ||
1302 | |||
1303 | cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); | ||
1304 | if (!cmd) { | ||
1305 | ret = -ENOMEM; | ||
1306 | goto out; | ||
1307 | } | ||
1308 | |||
1309 | ret = wl1271_cmd_send(wl, CMD_STOP_FWLOGGER, cmd, sizeof(*cmd), 0); | ||
1310 | if (ret < 0) { | ||
1311 | wl1271_error("failed to send stop firmware logger command"); | ||
1312 | goto out_free; | ||
1313 | } | ||
1314 | |||
1315 | out_free: | ||
1316 | kfree(cmd); | ||
1317 | |||
1318 | out: | ||
1319 | return ret; | ||
1320 | } | ||
diff --git a/drivers/net/wireless/wl12xx/cmd.h b/drivers/net/wireless/wl12xx/cmd.h index 5cac95d9480..1f7037292c1 100644 --- a/drivers/net/wireless/wl12xx/cmd.h +++ b/drivers/net/wireless/wl12xx/cmd.h | |||
@@ -70,6 +70,9 @@ int wl1271_cmd_start_bss(struct wl1271 *wl); | |||
70 | int wl1271_cmd_stop_bss(struct wl1271 *wl); | 70 | int wl1271_cmd_stop_bss(struct wl1271 *wl); |
71 | int wl1271_cmd_add_sta(struct wl1271 *wl, struct ieee80211_sta *sta, u8 hlid); | 71 | int wl1271_cmd_add_sta(struct wl1271 *wl, struct ieee80211_sta *sta, u8 hlid); |
72 | int wl1271_cmd_remove_sta(struct wl1271 *wl, u8 hlid); | 72 | int wl1271_cmd_remove_sta(struct wl1271 *wl, u8 hlid); |
73 | int wl12xx_cmd_config_fwlog(struct wl1271 *wl); | ||
74 | int wl12xx_cmd_start_fwlog(struct wl1271 *wl); | ||
75 | int wl12xx_cmd_stop_fwlog(struct wl1271 *wl); | ||
73 | 76 | ||
74 | enum wl1271_commands { | 77 | enum wl1271_commands { |
75 | CMD_INTERROGATE = 1, /*use this to read information elements*/ | 78 | CMD_INTERROGATE = 1, /*use this to read information elements*/ |
@@ -107,6 +110,9 @@ enum wl1271_commands { | |||
107 | CMD_START_PERIODIC_SCAN = 50, | 110 | CMD_START_PERIODIC_SCAN = 50, |
108 | CMD_STOP_PERIODIC_SCAN = 51, | 111 | CMD_STOP_PERIODIC_SCAN = 51, |
109 | CMD_SET_STA_STATE = 52, | 112 | CMD_SET_STA_STATE = 52, |
113 | CMD_CONFIG_FWLOGGER = 53, | ||
114 | CMD_START_FWLOGGER = 54, | ||
115 | CMD_STOP_FWLOGGER = 55, | ||
110 | 116 | ||
111 | /* AP mode commands */ | 117 | /* AP mode commands */ |
112 | CMD_BSS_START = 60, | 118 | CMD_BSS_START = 60, |
@@ -575,4 +581,60 @@ struct wl1271_cmd_remove_sta { | |||
575 | u8 padding1; | 581 | u8 padding1; |
576 | } __packed; | 582 | } __packed; |
577 | 583 | ||
584 | /* | ||
585 | * Continuous mode - packets are transferred to the host periodically | ||
586 | * via the data path. | ||
587 | * On demand - Log messages are stored in a cyclic buffer in the | ||
588 | * firmware, and only transferred to the host when explicitly requested | ||
589 | */ | ||
590 | enum wl12xx_fwlogger_log_mode { | ||
591 | WL12XX_FWLOG_CONTINUOUS, | ||
592 | WL12XX_FWLOG_ON_DEMAND | ||
593 | }; | ||
594 | |||
595 | /* Include/exclude timestamps from the log messages */ | ||
596 | enum wl12xx_fwlogger_timestamp { | ||
597 | WL12XX_FWLOG_TIMESTAMP_DISABLED, | ||
598 | WL12XX_FWLOG_TIMESTAMP_ENABLED | ||
599 | }; | ||
600 | |||
601 | /* | ||
602 | * Logs can be routed to the debug pinouts (where available), to the host bus | ||
603 | * (SDIO/SPI), or dropped | ||
604 | */ | ||
605 | enum wl12xx_fwlogger_output { | ||
606 | WL12XX_FWLOG_OUTPUT_NONE, | ||
607 | WL12XX_FWLOG_OUTPUT_DBG_PINS, | ||
608 | WL12XX_FWLOG_OUTPUT_HOST, | ||
609 | }; | ||
610 | |||
611 | struct wl12xx_cmd_config_fwlog { | ||
612 | struct wl1271_cmd_header header; | ||
613 | |||
614 | /* See enum wl12xx_fwlogger_log_mode */ | ||
615 | u8 logger_mode; | ||
616 | |||
617 | /* Minimum log level threshold */ | ||
618 | u8 log_severity; | ||
619 | |||
620 | /* Include/exclude timestamps from the log messages */ | ||
621 | u8 timestamp; | ||
622 | |||
623 | /* See enum wl1271_fwlogger_output */ | ||
624 | u8 output; | ||
625 | |||
626 | /* Regulates the frequency of log messages */ | ||
627 | u8 threshold; | ||
628 | |||
629 | u8 padding[3]; | ||
630 | } __packed; | ||
631 | |||
632 | struct wl12xx_cmd_start_fwlog { | ||
633 | struct wl1271_cmd_header header; | ||
634 | } __packed; | ||
635 | |||
636 | struct wl12xx_cmd_stop_fwlog { | ||
637 | struct wl1271_cmd_header header; | ||
638 | } __packed; | ||
639 | |||
578 | #endif /* __WL1271_CMD_H__ */ | 640 | #endif /* __WL1271_CMD_H__ */ |
diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index aa79b437e60..b5a7b30afda 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h | |||
@@ -1277,6 +1277,30 @@ struct conf_rx_streaming_settings { | |||
1277 | u8 always; | 1277 | u8 always; |
1278 | }; | 1278 | }; |
1279 | 1279 | ||
1280 | struct conf_fwlog { | ||
1281 | /* Continuous or on-demand */ | ||
1282 | u8 mode; | ||
1283 | |||
1284 | /* | ||
1285 | * Number of memory blocks dedicated for the FW logger | ||
1286 | * | ||
1287 | * Range: 1-3, or 0 to disable the FW logger | ||
1288 | */ | ||
1289 | u8 mem_blocks; | ||
1290 | |||
1291 | /* Minimum log level threshold */ | ||
1292 | u8 severity; | ||
1293 | |||
1294 | /* Include/exclude timestamps from the log messages */ | ||
1295 | u8 timestamp; | ||
1296 | |||
1297 | /* See enum wl1271_fwlogger_output */ | ||
1298 | u8 output; | ||
1299 | |||
1300 | /* Regulates the frequency of log messages */ | ||
1301 | u8 threshold; | ||
1302 | }; | ||
1303 | |||
1280 | struct conf_drv_settings { | 1304 | struct conf_drv_settings { |
1281 | struct conf_sg_settings sg; | 1305 | struct conf_sg_settings sg; |
1282 | struct conf_rx_settings rx; | 1306 | struct conf_rx_settings rx; |
@@ -1293,6 +1317,7 @@ struct conf_drv_settings { | |||
1293 | struct conf_memory_settings mem_wl128x; | 1317 | struct conf_memory_settings mem_wl128x; |
1294 | struct conf_fm_coex fm_coex; | 1318 | struct conf_fm_coex fm_coex; |
1295 | struct conf_rx_streaming_settings rx_streaming; | 1319 | struct conf_rx_streaming_settings rx_streaming; |
1320 | struct conf_fwlog fwlog; | ||
1296 | u8 hci_io_ds; | 1321 | u8 hci_io_ds; |
1297 | }; | 1322 | }; |
1298 | 1323 | ||
diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c index f5c2c9e6f84..cf40ac93cea 100644 --- a/drivers/net/wireless/wl12xx/init.c +++ b/drivers/net/wireless/wl12xx/init.c | |||
@@ -321,6 +321,20 @@ static int wl1271_init_beacon_broadcast(struct wl1271 *wl) | |||
321 | return 0; | 321 | return 0; |
322 | } | 322 | } |
323 | 323 | ||
324 | static int wl12xx_init_fwlog(struct wl1271 *wl) | ||
325 | { | ||
326 | int ret; | ||
327 | |||
328 | if (wl->quirks & WL12XX_QUIRK_FWLOG_NOT_IMPLEMENTED) | ||
329 | return 0; | ||
330 | |||
331 | ret = wl12xx_cmd_config_fwlog(wl); | ||
332 | if (ret < 0) | ||
333 | return ret; | ||
334 | |||
335 | return 0; | ||
336 | } | ||
337 | |||
324 | static int wl1271_sta_hw_init(struct wl1271 *wl) | 338 | static int wl1271_sta_hw_init(struct wl1271 *wl) |
325 | { | 339 | { |
326 | int ret; | 340 | int ret; |
@@ -382,6 +396,11 @@ static int wl1271_sta_hw_init(struct wl1271 *wl) | |||
382 | if (ret < 0) | 396 | if (ret < 0) |
383 | return ret; | 397 | return ret; |
384 | 398 | ||
399 | /* Configure the FW logger */ | ||
400 | ret = wl12xx_init_fwlog(wl); | ||
401 | if (ret < 0) | ||
402 | return ret; | ||
403 | |||
385 | return 0; | 404 | return 0; |
386 | } | 405 | } |
387 | 406 | ||
diff --git a/drivers/net/wireless/wl12xx/io.h b/drivers/net/wireless/wl12xx/io.h index beed621a8ae..cfb3588a4dd 100644 --- a/drivers/net/wireless/wl12xx/io.h +++ b/drivers/net/wireless/wl12xx/io.h | |||
@@ -128,6 +128,20 @@ static inline void wl1271_write(struct wl1271 *wl, int addr, void *buf, | |||
128 | wl1271_raw_write(wl, physical, buf, len, fixed); | 128 | wl1271_raw_write(wl, physical, buf, len, fixed); |
129 | } | 129 | } |
130 | 130 | ||
131 | static inline void wl1271_read_hwaddr(struct wl1271 *wl, int hwaddr, | ||
132 | void *buf, size_t len, bool fixed) | ||
133 | { | ||
134 | int physical; | ||
135 | int addr; | ||
136 | |||
137 | /* Addresses are stored internally as addresses to 32 bytes blocks */ | ||
138 | addr = hwaddr << 5; | ||
139 | |||
140 | physical = wl1271_translate_addr(wl, addr); | ||
141 | |||
142 | wl1271_raw_read(wl, physical, buf, len, fixed); | ||
143 | } | ||
144 | |||
131 | static inline u32 wl1271_read32(struct wl1271 *wl, int addr) | 145 | static inline u32 wl1271_read32(struct wl1271 *wl, int addr) |
132 | { | 146 | { |
133 | return wl1271_raw_read32(wl, wl1271_translate_addr(wl, addr)); | 147 | return wl1271_raw_read32(wl, wl1271_translate_addr(wl, addr)); |
diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 6926d0a3e5c..a3734bdf511 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c | |||
@@ -31,6 +31,7 @@ | |||
31 | #include <linux/platform_device.h> | 31 | #include <linux/platform_device.h> |
32 | #include <linux/slab.h> | 32 | #include <linux/slab.h> |
33 | #include <linux/wl12xx.h> | 33 | #include <linux/wl12xx.h> |
34 | #include <linux/sched.h> | ||
34 | 35 | ||
35 | #include "wl12xx.h" | 36 | #include "wl12xx.h" |
36 | #include "wl12xx_80211.h" | 37 | #include "wl12xx_80211.h" |
@@ -368,9 +369,19 @@ static struct conf_drv_settings default_conf = { | |||
368 | .interval = 20, | 369 | .interval = 20, |
369 | .always = 0, | 370 | .always = 0, |
370 | }, | 371 | }, |
372 | .fwlog = { | ||
373 | .mode = WL12XX_FWLOG_ON_DEMAND, | ||
374 | .mem_blocks = 2, | ||
375 | .severity = 0, | ||
376 | .timestamp = WL12XX_FWLOG_TIMESTAMP_DISABLED, | ||
377 | .output = WL12XX_FWLOG_OUTPUT_HOST, | ||
378 | .threshold = 0, | ||
379 | }, | ||
371 | .hci_io_ds = HCI_IO_DS_6MA, | 380 | .hci_io_ds = HCI_IO_DS_6MA, |
372 | }; | 381 | }; |
373 | 382 | ||
383 | static char *fwlog_param; | ||
384 | |||
374 | static void __wl1271_op_remove_interface(struct wl1271 *wl, | 385 | static void __wl1271_op_remove_interface(struct wl1271 *wl, |
375 | bool reset_tx_queues); | 386 | bool reset_tx_queues); |
376 | static void wl1271_free_ap_keys(struct wl1271 *wl); | 387 | static void wl1271_free_ap_keys(struct wl1271 *wl); |
@@ -617,8 +628,24 @@ static void wl1271_conf_init(struct wl1271 *wl) | |||
617 | 628 | ||
618 | /* apply driver default configuration */ | 629 | /* apply driver default configuration */ |
619 | memcpy(&wl->conf, &default_conf, sizeof(default_conf)); | 630 | memcpy(&wl->conf, &default_conf, sizeof(default_conf)); |
620 | } | ||
621 | 631 | ||
632 | /* Adjust settings according to optional module parameters */ | ||
633 | if (fwlog_param) { | ||
634 | if (!strcmp(fwlog_param, "continuous")) { | ||
635 | wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS; | ||
636 | } else if (!strcmp(fwlog_param, "ondemand")) { | ||
637 | wl->conf.fwlog.mode = WL12XX_FWLOG_ON_DEMAND; | ||
638 | } else if (!strcmp(fwlog_param, "dbgpins")) { | ||
639 | wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS; | ||
640 | wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_DBG_PINS; | ||
641 | } else if (!strcmp(fwlog_param, "disable")) { | ||
642 | wl->conf.fwlog.mem_blocks = 0; | ||
643 | wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_NONE; | ||
644 | } else { | ||
645 | wl1271_error("Unknown fwlog parameter %s", fwlog_param); | ||
646 | } | ||
647 | } | ||
648 | } | ||
622 | 649 | ||
623 | static int wl1271_plt_init(struct wl1271 *wl) | 650 | static int wl1271_plt_init(struct wl1271 *wl) |
624 | { | 651 | { |
@@ -1105,6 +1132,83 @@ void wl12xx_queue_recovery_work(struct wl1271 *wl) | |||
1105 | ieee80211_queue_work(wl->hw, &wl->recovery_work); | 1132 | ieee80211_queue_work(wl->hw, &wl->recovery_work); |
1106 | } | 1133 | } |
1107 | 1134 | ||
1135 | size_t wl12xx_copy_fwlog(struct wl1271 *wl, u8 *memblock, size_t maxlen) | ||
1136 | { | ||
1137 | size_t len = 0; | ||
1138 | |||
1139 | /* The FW log is a length-value list, find where the log end */ | ||
1140 | while (len < maxlen) { | ||
1141 | if (memblock[len] == 0) | ||
1142 | break; | ||
1143 | if (len + memblock[len] + 1 > maxlen) | ||
1144 | break; | ||
1145 | len += memblock[len] + 1; | ||
1146 | } | ||
1147 | |||
1148 | /* Make sure we have enough room */ | ||
1149 | len = min(len, (size_t)(PAGE_SIZE - wl->fwlog_size)); | ||
1150 | |||
1151 | /* Fill the FW log file, consumed by the sysfs fwlog entry */ | ||
1152 | memcpy(wl->fwlog + wl->fwlog_size, memblock, len); | ||
1153 | wl->fwlog_size += len; | ||
1154 | |||
1155 | return len; | ||
1156 | } | ||
1157 | |||
1158 | static void wl12xx_read_fwlog_panic(struct wl1271 *wl) | ||
1159 | { | ||
1160 | u32 addr; | ||
1161 | u32 first_addr; | ||
1162 | u8 *block; | ||
1163 | |||
1164 | if ((wl->quirks & WL12XX_QUIRK_FWLOG_NOT_IMPLEMENTED) || | ||
1165 | (wl->conf.fwlog.mode != WL12XX_FWLOG_ON_DEMAND) || | ||
1166 | (wl->conf.fwlog.mem_blocks == 0)) | ||
1167 | return; | ||
1168 | |||
1169 | wl1271_info("Reading FW panic log"); | ||
1170 | |||
1171 | block = kmalloc(WL12XX_HW_BLOCK_SIZE, GFP_KERNEL); | ||
1172 | if (!block) | ||
1173 | return; | ||
1174 | |||
1175 | /* | ||
1176 | * Make sure the chip is awake and the logger isn't active. | ||
1177 | * This might fail if the firmware hanged. | ||
1178 | */ | ||
1179 | if (!wl1271_ps_elp_wakeup(wl)) | ||
1180 | wl12xx_cmd_stop_fwlog(wl); | ||
1181 | |||
1182 | /* Read the first memory block address */ | ||
1183 | wl1271_fw_status(wl, wl->fw_status); | ||
1184 | first_addr = __le32_to_cpu(wl->fw_status->sta.log_start_addr); | ||
1185 | if (!first_addr) | ||
1186 | goto out; | ||
1187 | |||
1188 | /* Traverse the memory blocks linked list */ | ||
1189 | addr = first_addr; | ||
1190 | do { | ||
1191 | memset(block, 0, WL12XX_HW_BLOCK_SIZE); | ||
1192 | wl1271_read_hwaddr(wl, addr, block, WL12XX_HW_BLOCK_SIZE, | ||
1193 | false); | ||
1194 | |||
1195 | /* | ||
1196 | * Memory blocks are linked to one another. The first 4 bytes | ||
1197 | * of each memory block hold the hardware address of the next | ||
1198 | * one. The last memory block points to the first one. | ||
1199 | */ | ||
1200 | addr = __le32_to_cpup((__le32 *)block); | ||
1201 | if (!wl12xx_copy_fwlog(wl, block + sizeof(addr), | ||
1202 | WL12XX_HW_BLOCK_SIZE - sizeof(addr))) | ||
1203 | break; | ||
1204 | } while (addr && (addr != first_addr)); | ||
1205 | |||
1206 | wake_up_interruptible(&wl->fwlog_waitq); | ||
1207 | |||
1208 | out: | ||
1209 | kfree(block); | ||
1210 | } | ||
1211 | |||
1108 | static void wl1271_recovery_work(struct work_struct *work) | 1212 | static void wl1271_recovery_work(struct work_struct *work) |
1109 | { | 1213 | { |
1110 | struct wl1271 *wl = | 1214 | struct wl1271 *wl = |
@@ -1118,6 +1222,8 @@ static void wl1271_recovery_work(struct work_struct *work) | |||
1118 | /* Avoid a recursive recovery */ | 1222 | /* Avoid a recursive recovery */ |
1119 | set_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags); | 1223 | set_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags); |
1120 | 1224 | ||
1225 | wl12xx_read_fwlog_panic(wl); | ||
1226 | |||
1121 | wl1271_info("Hardware recovery in progress. FW ver: %s pc: 0x%x", | 1227 | wl1271_info("Hardware recovery in progress. FW ver: %s pc: 0x%x", |
1122 | wl->chip.fw_ver_str, wl1271_read32(wl, SCR_PAD4)); | 1228 | wl->chip.fw_ver_str, wl1271_read32(wl, SCR_PAD4)); |
1123 | 1229 | ||
@@ -3942,6 +4048,69 @@ static ssize_t wl1271_sysfs_show_hw_pg_ver(struct device *dev, | |||
3942 | static DEVICE_ATTR(hw_pg_ver, S_IRUGO | S_IWUSR, | 4048 | static DEVICE_ATTR(hw_pg_ver, S_IRUGO | S_IWUSR, |
3943 | wl1271_sysfs_show_hw_pg_ver, NULL); | 4049 | wl1271_sysfs_show_hw_pg_ver, NULL); |
3944 | 4050 | ||
4051 | static ssize_t wl1271_sysfs_read_fwlog(struct file *filp, struct kobject *kobj, | ||
4052 | struct bin_attribute *bin_attr, | ||
4053 | char *buffer, loff_t pos, size_t count) | ||
4054 | { | ||
4055 | struct device *dev = container_of(kobj, struct device, kobj); | ||
4056 | struct wl1271 *wl = dev_get_drvdata(dev); | ||
4057 | ssize_t len; | ||
4058 | int ret; | ||
4059 | |||
4060 | ret = mutex_lock_interruptible(&wl->mutex); | ||
4061 | if (ret < 0) | ||
4062 | return -ERESTARTSYS; | ||
4063 | |||
4064 | /* Let only one thread read the log at a time, blocking others */ | ||
4065 | while (wl->fwlog_size == 0) { | ||
4066 | DEFINE_WAIT(wait); | ||
4067 | |||
4068 | prepare_to_wait_exclusive(&wl->fwlog_waitq, | ||
4069 | &wait, | ||
4070 | TASK_INTERRUPTIBLE); | ||
4071 | |||
4072 | if (wl->fwlog_size != 0) { | ||
4073 | finish_wait(&wl->fwlog_waitq, &wait); | ||
4074 | break; | ||
4075 | } | ||
4076 | |||
4077 | mutex_unlock(&wl->mutex); | ||
4078 | |||
4079 | schedule(); | ||
4080 | finish_wait(&wl->fwlog_waitq, &wait); | ||
4081 | |||
4082 | if (signal_pending(current)) | ||
4083 | return -ERESTARTSYS; | ||
4084 | |||
4085 | ret = mutex_lock_interruptible(&wl->mutex); | ||
4086 | if (ret < 0) | ||
4087 | return -ERESTARTSYS; | ||
4088 | } | ||
4089 | |||
4090 | /* Check if the fwlog is still valid */ | ||
4091 | if (wl->fwlog_size < 0) { | ||
4092 | mutex_unlock(&wl->mutex); | ||
4093 | return 0; | ||
4094 | } | ||
4095 | |||
4096 | /* Seeking is not supported - old logs are not kept. Disregard pos. */ | ||
4097 | len = min(count, (size_t)wl->fwlog_size); | ||
4098 | wl->fwlog_size -= len; | ||
4099 | memcpy(buffer, wl->fwlog, len); | ||
4100 | |||
4101 | /* Make room for new messages */ | ||
4102 | memmove(wl->fwlog, wl->fwlog + len, wl->fwlog_size); | ||
4103 | |||
4104 | mutex_unlock(&wl->mutex); | ||
4105 | |||
4106 | return len; | ||
4107 | } | ||
4108 | |||
4109 | static struct bin_attribute fwlog_attr = { | ||
4110 | .attr = {.name = "fwlog", .mode = S_IRUSR}, | ||
4111 | .read = wl1271_sysfs_read_fwlog, | ||
4112 | }; | ||
4113 | |||
3945 | int wl1271_register_hw(struct wl1271 *wl) | 4114 | int wl1271_register_hw(struct wl1271 *wl) |
3946 | { | 4115 | { |
3947 | int ret; | 4116 | int ret; |
@@ -4160,6 +4329,8 @@ struct ieee80211_hw *wl1271_alloc_hw(void) | |||
4160 | wl->sched_scanning = false; | 4329 | wl->sched_scanning = false; |
4161 | setup_timer(&wl->rx_streaming_timer, wl1271_rx_streaming_timer, | 4330 | setup_timer(&wl->rx_streaming_timer, wl1271_rx_streaming_timer, |
4162 | (unsigned long) wl); | 4331 | (unsigned long) wl); |
4332 | wl->fwlog_size = 0; | ||
4333 | init_waitqueue_head(&wl->fwlog_waitq); | ||
4163 | 4334 | ||
4164 | memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map)); | 4335 | memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map)); |
4165 | for (i = 0; i < ACX_TX_DESCRIPTORS; i++) | 4336 | for (i = 0; i < ACX_TX_DESCRIPTORS; i++) |
@@ -4186,11 +4357,18 @@ struct ieee80211_hw *wl1271_alloc_hw(void) | |||
4186 | goto err_aggr; | 4357 | goto err_aggr; |
4187 | } | 4358 | } |
4188 | 4359 | ||
4360 | /* Allocate one page for the FW log */ | ||
4361 | wl->fwlog = (u8 *)get_zeroed_page(GFP_KERNEL); | ||
4362 | if (!wl->fwlog) { | ||
4363 | ret = -ENOMEM; | ||
4364 | goto err_dummy_packet; | ||
4365 | } | ||
4366 | |||
4189 | /* Register platform device */ | 4367 | /* Register platform device */ |
4190 | ret = platform_device_register(wl->plat_dev); | 4368 | ret = platform_device_register(wl->plat_dev); |
4191 | if (ret) { | 4369 | if (ret) { |
4192 | wl1271_error("couldn't register platform device"); | 4370 | wl1271_error("couldn't register platform device"); |
4193 | goto err_dummy_packet; | 4371 | goto err_fwlog; |
4194 | } | 4372 | } |
4195 | dev_set_drvdata(&wl->plat_dev->dev, wl); | 4373 | dev_set_drvdata(&wl->plat_dev->dev, wl); |
4196 | 4374 | ||
@@ -4208,14 +4386,27 @@ struct ieee80211_hw *wl1271_alloc_hw(void) | |||
4208 | goto err_bt_coex_state; | 4386 | goto err_bt_coex_state; |
4209 | } | 4387 | } |
4210 | 4388 | ||
4389 | /* Create sysfs file for the FW log */ | ||
4390 | ret = device_create_bin_file(&wl->plat_dev->dev, &fwlog_attr); | ||
4391 | if (ret < 0) { | ||
4392 | wl1271_error("failed to create sysfs file fwlog"); | ||
4393 | goto err_hw_pg_ver; | ||
4394 | } | ||
4395 | |||
4211 | return hw; | 4396 | return hw; |
4212 | 4397 | ||
4398 | err_hw_pg_ver: | ||
4399 | device_remove_file(&wl->plat_dev->dev, &dev_attr_hw_pg_ver); | ||
4400 | |||
4213 | err_bt_coex_state: | 4401 | err_bt_coex_state: |
4214 | device_remove_file(&wl->plat_dev->dev, &dev_attr_bt_coex_state); | 4402 | device_remove_file(&wl->plat_dev->dev, &dev_attr_bt_coex_state); |
4215 | 4403 | ||
4216 | err_platform: | 4404 | err_platform: |
4217 | platform_device_unregister(wl->plat_dev); | 4405 | platform_device_unregister(wl->plat_dev); |
4218 | 4406 | ||
4407 | err_fwlog: | ||
4408 | free_page((unsigned long)wl->fwlog); | ||
4409 | |||
4219 | err_dummy_packet: | 4410 | err_dummy_packet: |
4220 | dev_kfree_skb(wl->dummy_packet); | 4411 | dev_kfree_skb(wl->dummy_packet); |
4221 | 4412 | ||
@@ -4240,7 +4431,15 @@ EXPORT_SYMBOL_GPL(wl1271_alloc_hw); | |||
4240 | 4431 | ||
4241 | int wl1271_free_hw(struct wl1271 *wl) | 4432 | int wl1271_free_hw(struct wl1271 *wl) |
4242 | { | 4433 | { |
4434 | /* Unblock any fwlog readers */ | ||
4435 | mutex_lock(&wl->mutex); | ||
4436 | wl->fwlog_size = -1; | ||
4437 | wake_up_interruptible_all(&wl->fwlog_waitq); | ||
4438 | mutex_unlock(&wl->mutex); | ||
4439 | |||
4440 | device_remove_bin_file(&wl->plat_dev->dev, &fwlog_attr); | ||
4243 | platform_device_unregister(wl->plat_dev); | 4441 | platform_device_unregister(wl->plat_dev); |
4442 | free_page((unsigned long)wl->fwlog); | ||
4244 | dev_kfree_skb(wl->dummy_packet); | 4443 | dev_kfree_skb(wl->dummy_packet); |
4245 | free_pages((unsigned long)wl->aggr_buf, | 4444 | free_pages((unsigned long)wl->aggr_buf, |
4246 | get_order(WL1271_AGGR_BUFFER_SIZE)); | 4445 | get_order(WL1271_AGGR_BUFFER_SIZE)); |
@@ -4268,6 +4467,10 @@ EXPORT_SYMBOL_GPL(wl12xx_debug_level); | |||
4268 | module_param_named(debug_level, wl12xx_debug_level, uint, S_IRUSR | S_IWUSR); | 4467 | module_param_named(debug_level, wl12xx_debug_level, uint, S_IRUSR | S_IWUSR); |
4269 | MODULE_PARM_DESC(debug_level, "wl12xx debugging level"); | 4468 | MODULE_PARM_DESC(debug_level, "wl12xx debugging level"); |
4270 | 4469 | ||
4470 | module_param_named(fwlog, fwlog_param, charp, 0); | ||
4471 | MODULE_PARM_DESC(keymap, | ||
4472 | "FW logger options: continuous, ondemand, dbgpins or disable"); | ||
4473 | |||
4271 | MODULE_LICENSE("GPL"); | 4474 | MODULE_LICENSE("GPL"); |
4272 | MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>"); | 4475 | MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>"); |
4273 | MODULE_AUTHOR("Juuso Oikarinen <juuso.oikarinen@nokia.com>"); | 4476 | MODULE_AUTHOR("Juuso Oikarinen <juuso.oikarinen@nokia.com>"); |
diff --git a/drivers/net/wireless/wl12xx/rx.c b/drivers/net/wireless/wl12xx/rx.c index 9357695340c..0450fb49dbb 100644 --- a/drivers/net/wireless/wl12xx/rx.c +++ b/drivers/net/wireless/wl12xx/rx.c | |||
@@ -22,6 +22,7 @@ | |||
22 | */ | 22 | */ |
23 | 23 | ||
24 | #include <linux/gfp.h> | 24 | #include <linux/gfp.h> |
25 | #include <linux/sched.h> | ||
25 | 26 | ||
26 | #include "wl12xx.h" | 27 | #include "wl12xx.h" |
27 | #include "acx.h" | 28 | #include "acx.h" |
@@ -107,6 +108,13 @@ static int wl1271_rx_handle_data(struct wl1271 *wl, u8 *data, u32 length) | |||
107 | /* the data read starts with the descriptor */ | 108 | /* the data read starts with the descriptor */ |
108 | desc = (struct wl1271_rx_descriptor *) data; | 109 | desc = (struct wl1271_rx_descriptor *) data; |
109 | 110 | ||
111 | if (desc->packet_class == WL12XX_RX_CLASS_LOGGER) { | ||
112 | size_t len = length - sizeof(*desc); | ||
113 | wl12xx_copy_fwlog(wl, data + sizeof(*desc), len); | ||
114 | wake_up_interruptible(&wl->fwlog_waitq); | ||
115 | return 0; | ||
116 | } | ||
117 | |||
110 | switch (desc->status & WL1271_RX_DESC_STATUS_MASK) { | 118 | switch (desc->status & WL1271_RX_DESC_STATUS_MASK) { |
111 | /* discard corrupted packets */ | 119 | /* discard corrupted packets */ |
112 | case WL1271_RX_DESC_DRIVER_RX_Q_FAIL: | 120 | case WL1271_RX_DESC_DRIVER_RX_Q_FAIL: |
diff --git a/drivers/net/wireless/wl12xx/rx.h b/drivers/net/wireless/wl12xx/rx.h index 75fabf83649..c88e3fa1d60 100644 --- a/drivers/net/wireless/wl12xx/rx.h +++ b/drivers/net/wireless/wl12xx/rx.h | |||
@@ -97,6 +97,18 @@ | |||
97 | #define RX_BUF_SIZE_MASK 0xFFF00 | 97 | #define RX_BUF_SIZE_MASK 0xFFF00 |
98 | #define RX_BUF_SIZE_SHIFT_DIV 6 | 98 | #define RX_BUF_SIZE_SHIFT_DIV 6 |
99 | 99 | ||
100 | enum { | ||
101 | WL12XX_RX_CLASS_UNKNOWN, | ||
102 | WL12XX_RX_CLASS_MANAGEMENT, | ||
103 | WL12XX_RX_CLASS_DATA, | ||
104 | WL12XX_RX_CLASS_QOS_DATA, | ||
105 | WL12XX_RX_CLASS_BCN_PRBRSP, | ||
106 | WL12XX_RX_CLASS_EAPOL, | ||
107 | WL12XX_RX_CLASS_BA_EVENT, | ||
108 | WL12XX_RX_CLASS_AMSDU, | ||
109 | WL12XX_RX_CLASS_LOGGER, | ||
110 | }; | ||
111 | |||
100 | struct wl1271_rx_descriptor { | 112 | struct wl1271_rx_descriptor { |
101 | __le16 length; | 113 | __le16 length; |
102 | u8 status; | 114 | u8 status; |
diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 754a16ce5bc..d7db6e77047 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h | |||
@@ -226,6 +226,8 @@ enum { | |||
226 | #define FW_VER_MINOR_1_SPARE_STA_MIN 58 | 226 | #define FW_VER_MINOR_1_SPARE_STA_MIN 58 |
227 | #define FW_VER_MINOR_1_SPARE_AP_MIN 47 | 227 | #define FW_VER_MINOR_1_SPARE_AP_MIN 47 |
228 | 228 | ||
229 | #define FW_VER_MINOR_FWLOG_STA_MIN 70 | ||
230 | |||
229 | struct wl1271_chip { | 231 | struct wl1271_chip { |
230 | u32 id; | 232 | u32 id; |
231 | char fw_ver_str[ETHTOOL_BUSINFO_LEN]; | 233 | char fw_ver_str[ETHTOOL_BUSINFO_LEN]; |
@@ -284,8 +286,7 @@ struct wl1271_fw_sta_status { | |||
284 | u8 tx_total; | 286 | u8 tx_total; |
285 | u8 reserved1; | 287 | u8 reserved1; |
286 | __le16 reserved2; | 288 | __le16 reserved2; |
287 | /* Total structure size is 68 bytes */ | 289 | __le32 log_start_addr; |
288 | u32 padding; | ||
289 | } __packed; | 290 | } __packed; |
290 | 291 | ||
291 | struct wl1271_fw_full_status { | 292 | struct wl1271_fw_full_status { |
@@ -472,6 +473,15 @@ struct wl1271 { | |||
472 | /* Network stack work */ | 473 | /* Network stack work */ |
473 | struct work_struct netstack_work; | 474 | struct work_struct netstack_work; |
474 | 475 | ||
476 | /* FW log buffer */ | ||
477 | u8 *fwlog; | ||
478 | |||
479 | /* Number of valid bytes in the FW log buffer */ | ||
480 | ssize_t fwlog_size; | ||
481 | |||
482 | /* Sysfs FW log entry readers wait queue */ | ||
483 | wait_queue_head_t fwlog_waitq; | ||
484 | |||
475 | /* Hardware recovery work */ | 485 | /* Hardware recovery work */ |
476 | struct work_struct recovery_work; | 486 | struct work_struct recovery_work; |
477 | 487 | ||
@@ -614,6 +624,7 @@ int wl1271_plt_start(struct wl1271 *wl); | |||
614 | int wl1271_plt_stop(struct wl1271 *wl); | 624 | int wl1271_plt_stop(struct wl1271 *wl); |
615 | int wl1271_recalc_rx_streaming(struct wl1271 *wl); | 625 | int wl1271_recalc_rx_streaming(struct wl1271 *wl); |
616 | void wl12xx_queue_recovery_work(struct wl1271 *wl); | 626 | void wl12xx_queue_recovery_work(struct wl1271 *wl); |
627 | size_t wl12xx_copy_fwlog(struct wl1271 *wl, u8 *memblock, size_t maxlen); | ||
617 | 628 | ||
618 | #define JOIN_TIMEOUT 5000 /* 5000 milliseconds to join */ | 629 | #define JOIN_TIMEOUT 5000 /* 5000 milliseconds to join */ |
619 | 630 | ||
@@ -655,4 +666,9 @@ void wl12xx_queue_recovery_work(struct wl1271 *wl); | |||
655 | */ | 666 | */ |
656 | #define WL12XX_QUIRK_LPD_MODE BIT(3) | 667 | #define WL12XX_QUIRK_LPD_MODE BIT(3) |
657 | 668 | ||
669 | /* Older firmwares did not implement the FW logger over bus feature */ | ||
670 | #define WL12XX_QUIRK_FWLOG_NOT_IMPLEMENTED BIT(4) | ||
671 | |||
672 | #define WL12XX_HW_BLOCK_SIZE 256 | ||
673 | |||
658 | #endif | 674 | #endif |