diff options
-rw-r--r-- | drivers/net/wireless/ipw2200.c | 251 | ||||
-rw-r--r-- | drivers/net/wireless/ipw2200.h | 30 |
2 files changed, 204 insertions, 77 deletions
diff --git a/drivers/net/wireless/ipw2200.c b/drivers/net/wireless/ipw2200.c index 6e862c2905de..a3283caaa872 100644 --- a/drivers/net/wireless/ipw2200.c +++ b/drivers/net/wireless/ipw2200.c | |||
@@ -428,6 +428,7 @@ static inline void ipw_disable_interrupts(struct ipw_priv *priv) | |||
428 | ipw_write32(priv, IPW_INTA_MASK_R, ~IPW_INTA_MASK_ALL); | 428 | ipw_write32(priv, IPW_INTA_MASK_R, ~IPW_INTA_MASK_ALL); |
429 | } | 429 | } |
430 | 430 | ||
431 | #ifdef CONFIG_IPW_DEBUG | ||
431 | static char *ipw_error_desc(u32 val) | 432 | static char *ipw_error_desc(u32 val) |
432 | { | 433 | { |
433 | switch (val) { | 434 | switch (val) { |
@@ -466,56 +467,35 @@ static char *ipw_error_desc(u32 val) | |||
466 | } | 467 | } |
467 | } | 468 | } |
468 | 469 | ||
469 | static void ipw_dump_nic_error_log(struct ipw_priv *priv) | 470 | static void ipw_dump_error_log(struct ipw_priv *priv, |
471 | struct ipw_fw_error *error) | ||
470 | { | 472 | { |
471 | u32 desc, time, blink1, blink2, ilink1, ilink2, idata, i, count, base; | 473 | u32 i; |
472 | |||
473 | base = ipw_read32(priv, IPWSTATUS_ERROR_LOG); | ||
474 | count = ipw_read_reg32(priv, base); | ||
475 | 474 | ||
476 | if (ERROR_START_OFFSET <= count * ERROR_ELEM_SIZE) { | 475 | if (!error) { |
477 | IPW_ERROR("Start IPW Error Log Dump:\n"); | 476 | IPW_ERROR("Error allocating and capturing error log. " |
478 | IPW_ERROR("Status: 0x%08X, Config: %08X\n", | 477 | "Nothing to dump.\n"); |
479 | priv->status, priv->config); | 478 | return; |
480 | } | 479 | } |
481 | 480 | ||
482 | for (i = ERROR_START_OFFSET; | 481 | IPW_ERROR("Start IPW Error Log Dump:\n"); |
483 | i <= count * ERROR_ELEM_SIZE; i += ERROR_ELEM_SIZE) { | 482 | IPW_ERROR("Status: 0x%08X, Config: %08X\n", |
484 | desc = ipw_read_reg32(priv, base + i); | 483 | error->status, error->config); |
485 | time = ipw_read_reg32(priv, base + i + 1 * sizeof(u32)); | ||
486 | blink1 = ipw_read_reg32(priv, base + i + 2 * sizeof(u32)); | ||
487 | blink2 = ipw_read_reg32(priv, base + i + 3 * sizeof(u32)); | ||
488 | ilink1 = ipw_read_reg32(priv, base + i + 4 * sizeof(u32)); | ||
489 | ilink2 = ipw_read_reg32(priv, base + i + 5 * sizeof(u32)); | ||
490 | idata = ipw_read_reg32(priv, base + i + 6 * sizeof(u32)); | ||
491 | 484 | ||
485 | for (i = 0; i < error->elem_len; i++) | ||
492 | IPW_ERROR("%s %i 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", | 486 | IPW_ERROR("%s %i 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", |
493 | ipw_error_desc(desc), time, blink1, blink2, | 487 | ipw_error_desc(error->elem[i].desc), |
494 | ilink1, ilink2, idata); | 488 | error->elem[i].time, |
495 | } | 489 | error->elem[i].blink1, |
490 | error->elem[i].blink2, | ||
491 | error->elem[i].link1, | ||
492 | error->elem[i].link2, error->elem[i].data); | ||
493 | for (i = 0; i < error->log_len; i++) | ||
494 | IPW_ERROR("%i\t0x%08x\t%i\n", | ||
495 | error->log[i].time, | ||
496 | error->log[i].event, error->log[i].data); | ||
496 | } | 497 | } |
497 | |||
498 | static void ipw_dump_nic_event_log(struct ipw_priv *priv) | ||
499 | { | ||
500 | u32 ev, time, data, i, count, base; | ||
501 | |||
502 | base = ipw_read32(priv, IPW_EVENT_LOG); | ||
503 | count = ipw_read_reg32(priv, base); | ||
504 | |||
505 | if (EVENT_START_OFFSET <= count * EVENT_ELEM_SIZE) | ||
506 | IPW_ERROR("Start IPW Event Log Dump:\n"); | ||
507 | |||
508 | for (i = EVENT_START_OFFSET; | ||
509 | i <= count * EVENT_ELEM_SIZE; i += EVENT_ELEM_SIZE) { | ||
510 | ev = ipw_read_reg32(priv, base + i); | ||
511 | time = ipw_read_reg32(priv, base + i + 1 * sizeof(u32)); | ||
512 | data = ipw_read_reg32(priv, base + i + 2 * sizeof(u32)); | ||
513 | |||
514 | #ifdef CONFIG_IPW_DEBUG | ||
515 | IPW_ERROR("%i\t0x%08x\t%i\n", time, data, ev); | ||
516 | #endif | 498 | #endif |
517 | } | ||
518 | } | ||
519 | 499 | ||
520 | static inline int ipw_is_init(struct ipw_priv *priv) | 500 | static inline int ipw_is_init(struct ipw_priv *priv) |
521 | { | 501 | { |
@@ -1058,6 +1038,130 @@ static ssize_t store_debug_level(struct device_driver *d, const char *buf, | |||
1058 | static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, | 1038 | static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, |
1059 | show_debug_level, store_debug_level); | 1039 | show_debug_level, store_debug_level); |
1060 | 1040 | ||
1041 | static inline u32 ipw_get_event_log_len(struct ipw_priv *priv) | ||
1042 | { | ||
1043 | return ipw_read_reg32(priv, ipw_read32(priv, IPW_EVENT_LOG)); | ||
1044 | } | ||
1045 | |||
1046 | static void ipw_capture_event_log(struct ipw_priv *priv, | ||
1047 | u32 log_len, struct ipw_event *log) | ||
1048 | { | ||
1049 | u32 base; | ||
1050 | |||
1051 | if (log_len) { | ||
1052 | base = ipw_read32(priv, IPW_EVENT_LOG); | ||
1053 | ipw_read_indirect(priv, base + sizeof(base) + sizeof(u32), | ||
1054 | (u8 *) log, sizeof(*log) * log_len); | ||
1055 | } | ||
1056 | } | ||
1057 | |||
1058 | static struct ipw_fw_error *ipw_alloc_error_log(struct ipw_priv *priv) | ||
1059 | { | ||
1060 | struct ipw_fw_error *error; | ||
1061 | u32 log_len = ipw_get_event_log_len(priv); | ||
1062 | u32 base = ipw_read32(priv, IPW_ERROR_LOG); | ||
1063 | u32 elem_len = ipw_read_reg32(priv, base); | ||
1064 | |||
1065 | error = kmalloc(sizeof(*error) + | ||
1066 | sizeof(*error->elem) * elem_len + | ||
1067 | sizeof(*error->log) * log_len, GFP_ATOMIC); | ||
1068 | if (!error) { | ||
1069 | IPW_ERROR("Memory allocation for firmware error log " | ||
1070 | "failed.\n"); | ||
1071 | return NULL; | ||
1072 | } | ||
1073 | error->status = priv->status; | ||
1074 | error->config = priv->config; | ||
1075 | error->elem_len = elem_len; | ||
1076 | error->log_len = log_len; | ||
1077 | error->elem = (struct ipw_error_elem *)error->payload; | ||
1078 | error->log = (struct ipw_event *)(error->elem + | ||
1079 | (sizeof(*error->elem) * elem_len)); | ||
1080 | |||
1081 | ipw_capture_event_log(priv, log_len, error->log); | ||
1082 | |||
1083 | if (elem_len) | ||
1084 | ipw_read_indirect(priv, base + sizeof(base), (u8 *) error->elem, | ||
1085 | sizeof(*error->elem) * elem_len); | ||
1086 | |||
1087 | return error; | ||
1088 | } | ||
1089 | |||
1090 | static void ipw_free_error_log(struct ipw_fw_error *error) | ||
1091 | { | ||
1092 | if (error) | ||
1093 | kfree(error); | ||
1094 | } | ||
1095 | |||
1096 | static ssize_t show_event_log(struct device *d, | ||
1097 | struct device_attribute *attr, char *buf) | ||
1098 | { | ||
1099 | struct ipw_priv *priv = dev_get_drvdata(d); | ||
1100 | u32 log_len = ipw_get_event_log_len(priv); | ||
1101 | struct ipw_event log[log_len]; | ||
1102 | u32 len = 0, i; | ||
1103 | |||
1104 | ipw_capture_event_log(priv, log_len, log); | ||
1105 | |||
1106 | len += snprintf(buf + len, PAGE_SIZE - len, "%08X", log_len); | ||
1107 | for (i = 0; i < log_len; i++) | ||
1108 | len += snprintf(buf + len, PAGE_SIZE - len, | ||
1109 | "\n%08X%08X%08X", | ||
1110 | log[i].time, log[i].event, log[i].data); | ||
1111 | len += snprintf(buf + len, PAGE_SIZE - len, "\n"); | ||
1112 | return len; | ||
1113 | } | ||
1114 | |||
1115 | static DEVICE_ATTR(event_log, S_IRUGO, show_event_log, NULL); | ||
1116 | |||
1117 | static ssize_t show_error(struct device *d, | ||
1118 | struct device_attribute *attr, char *buf) | ||
1119 | { | ||
1120 | struct ipw_priv *priv = dev_get_drvdata(d); | ||
1121 | u32 len = 0, i; | ||
1122 | if (!priv->error) | ||
1123 | return 0; | ||
1124 | len += snprintf(buf + len, PAGE_SIZE - len, | ||
1125 | "%08X%08X%08X", | ||
1126 | priv->error->status, | ||
1127 | priv->error->config, priv->error->elem_len); | ||
1128 | for (i = 0; i < priv->error->elem_len; i++) | ||
1129 | len += snprintf(buf + len, PAGE_SIZE - len, | ||
1130 | "\n%08X%08X%08X%08X%08X%08X%08X", | ||
1131 | priv->error->elem[i].time, | ||
1132 | priv->error->elem[i].desc, | ||
1133 | priv->error->elem[i].blink1, | ||
1134 | priv->error->elem[i].blink2, | ||
1135 | priv->error->elem[i].link1, | ||
1136 | priv->error->elem[i].link2, | ||
1137 | priv->error->elem[i].data); | ||
1138 | |||
1139 | len += snprintf(buf + len, PAGE_SIZE - len, | ||
1140 | "\n%08X", priv->error->log_len); | ||
1141 | for (i = 0; i < priv->error->log_len; i++) | ||
1142 | len += snprintf(buf + len, PAGE_SIZE - len, | ||
1143 | "\n%08X%08X%08X", | ||
1144 | priv->error->log[i].time, | ||
1145 | priv->error->log[i].event, | ||
1146 | priv->error->log[i].data); | ||
1147 | len += snprintf(buf + len, PAGE_SIZE - len, "\n"); | ||
1148 | return len; | ||
1149 | } | ||
1150 | |||
1151 | static ssize_t clear_error(struct device *d, | ||
1152 | struct device_attribute *attr, | ||
1153 | const char *buf, size_t count) | ||
1154 | { | ||
1155 | struct ipw_priv *priv = dev_get_drvdata(d); | ||
1156 | if (priv->error) { | ||
1157 | ipw_free_error_log(priv->error); | ||
1158 | priv->error = NULL; | ||
1159 | } | ||
1160 | return count; | ||
1161 | } | ||
1162 | |||
1163 | static DEVICE_ATTR(error, S_IRUGO | S_IWUSR, show_error, clear_error); | ||
1164 | |||
1061 | static ssize_t show_scan_age(struct device *d, struct device_attribute *attr, | 1165 | static ssize_t show_scan_age(struct device *d, struct device_attribute *attr, |
1062 | char *buf) | 1166 | char *buf) |
1063 | { | 1167 | { |
@@ -1163,34 +1267,6 @@ static ssize_t show_nic_type(struct device *d, | |||
1163 | 1267 | ||
1164 | static DEVICE_ATTR(nic_type, S_IRUGO, show_nic_type, NULL); | 1268 | static DEVICE_ATTR(nic_type, S_IRUGO, show_nic_type, NULL); |
1165 | 1269 | ||
1166 | static ssize_t dump_error_log(struct device *d, | ||
1167 | struct device_attribute *attr, const char *buf, | ||
1168 | size_t count) | ||
1169 | { | ||
1170 | char *p = (char *)buf; | ||
1171 | |||
1172 | if (p[0] == '1') | ||
1173 | ipw_dump_nic_error_log((struct ipw_priv *)d->driver_data); | ||
1174 | |||
1175 | return strnlen(buf, count); | ||
1176 | } | ||
1177 | |||
1178 | static DEVICE_ATTR(dump_errors, S_IWUSR, NULL, dump_error_log); | ||
1179 | |||
1180 | static ssize_t dump_event_log(struct device *d, | ||
1181 | struct device_attribute *attr, const char *buf, | ||
1182 | size_t count) | ||
1183 | { | ||
1184 | char *p = (char *)buf; | ||
1185 | |||
1186 | if (p[0] == '1') | ||
1187 | ipw_dump_nic_event_log((struct ipw_priv *)d->driver_data); | ||
1188 | |||
1189 | return strnlen(buf, count); | ||
1190 | } | ||
1191 | |||
1192 | static DEVICE_ATTR(dump_events, S_IWUSR, NULL, dump_event_log); | ||
1193 | |||
1194 | static ssize_t show_ucode_version(struct device *d, | 1270 | static ssize_t show_ucode_version(struct device *d, |
1195 | struct device_attribute *attr, char *buf) | 1271 | struct device_attribute *attr, char *buf) |
1196 | { | 1272 | { |
@@ -1614,12 +1690,30 @@ static void ipw_irq_tasklet(struct ipw_priv *priv) | |||
1614 | 1690 | ||
1615 | if (inta & IPW_INTA_BIT_FATAL_ERROR) { | 1691 | if (inta & IPW_INTA_BIT_FATAL_ERROR) { |
1616 | IPW_ERROR("Firmware error detected. Restarting.\n"); | 1692 | IPW_ERROR("Firmware error detected. Restarting.\n"); |
1693 | if (priv->error) { | ||
1694 | IPW_ERROR("Sysfs 'error' log already exists.\n"); | ||
1617 | #ifdef CONFIG_IPW_DEBUG | 1695 | #ifdef CONFIG_IPW_DEBUG |
1618 | if (ipw_debug_level & IPW_DL_FW_ERRORS) { | 1696 | if (ipw_debug_level & IPW_DL_FW_ERRORS) { |
1619 | ipw_dump_nic_error_log(priv); | 1697 | struct ipw_fw_error *error = |
1620 | ipw_dump_nic_event_log(priv); | 1698 | ipw_alloc_error_log(priv); |
1621 | } | 1699 | ipw_dump_error_log(priv, error); |
1700 | if (error) | ||
1701 | ipw_free_error_log(error); | ||
1702 | } | ||
1703 | #endif | ||
1704 | } else { | ||
1705 | priv->error = ipw_alloc_error_log(priv); | ||
1706 | if (priv->error) | ||
1707 | IPW_ERROR("Sysfs 'error' log captured.\n"); | ||
1708 | else | ||
1709 | IPW_ERROR("Error allocating sysfs 'error' " | ||
1710 | "log.\n"); | ||
1711 | #ifdef CONFIG_IPW_DEBUG | ||
1712 | if (ipw_debug_level & IPW_DL_FW_ERRORS) | ||
1713 | ipw_dump_error_log(priv, priv->error); | ||
1622 | #endif | 1714 | #endif |
1715 | } | ||
1716 | |||
1623 | /* XXX: If hardware encryption is for WPA/WPA2, | 1717 | /* XXX: If hardware encryption is for WPA/WPA2, |
1624 | * we have to notify the supplicant. */ | 1718 | * we have to notify the supplicant. */ |
1625 | if (priv->ieee->sec.encrypt) { | 1719 | if (priv->ieee->sec.encrypt) { |
@@ -10958,8 +11052,8 @@ static struct attribute *ipw_sysfs_entries[] = { | |||
10958 | &dev_attr_nic_type.attr, | 11052 | &dev_attr_nic_type.attr, |
10959 | &dev_attr_status.attr, | 11053 | &dev_attr_status.attr, |
10960 | &dev_attr_cfg.attr, | 11054 | &dev_attr_cfg.attr, |
10961 | &dev_attr_dump_errors.attr, | 11055 | &dev_attr_error.attr, |
10962 | &dev_attr_dump_events.attr, | 11056 | &dev_attr_event_log.attr, |
10963 | &dev_attr_eeprom_delay.attr, | 11057 | &dev_attr_eeprom_delay.attr, |
10964 | &dev_attr_ucode_version.attr, | 11058 | &dev_attr_ucode_version.attr, |
10965 | &dev_attr_rtc.attr, | 11059 | &dev_attr_rtc.attr, |
@@ -11172,6 +11266,11 @@ static void ipw_pci_remove(struct pci_dev *pdev) | |||
11172 | } | 11266 | } |
11173 | } | 11267 | } |
11174 | 11268 | ||
11269 | if (priv->error) { | ||
11270 | ipw_free_error_log(priv->error); | ||
11271 | priv->error = NULL; | ||
11272 | } | ||
11273 | |||
11175 | free_irq(pdev->irq, priv); | 11274 | free_irq(pdev->irq, priv); |
11176 | iounmap(priv->hw_base); | 11275 | iounmap(priv->hw_base); |
11177 | pci_release_regions(pdev); | 11276 | pci_release_regions(pdev); |
diff --git a/drivers/net/wireless/ipw2200.h b/drivers/net/wireless/ipw2200.h index 9bf8aa427d8c..9bf03ac55390 100644 --- a/drivers/net/wireless/ipw2200.h +++ b/drivers/net/wireless/ipw2200.h | |||
@@ -1085,6 +1085,32 @@ struct ipw_ibss_seq { | |||
1085 | struct list_head list; | 1085 | struct list_head list; |
1086 | }; | 1086 | }; |
1087 | 1087 | ||
1088 | struct ipw_error_elem { | ||
1089 | u32 desc; | ||
1090 | u32 time; | ||
1091 | u32 blink1; | ||
1092 | u32 blink2; | ||
1093 | u32 link1; | ||
1094 | u32 link2; | ||
1095 | u32 data; | ||
1096 | }; | ||
1097 | |||
1098 | struct ipw_event { | ||
1099 | u32 event; | ||
1100 | u32 time; | ||
1101 | u32 data; | ||
1102 | } __attribute__ ((packed)); | ||
1103 | |||
1104 | struct ipw_fw_error { | ||
1105 | u32 status; | ||
1106 | u32 config; | ||
1107 | u32 elem_len; | ||
1108 | u32 log_len; | ||
1109 | struct ipw_error_elem *elem; | ||
1110 | struct ipw_event *log; | ||
1111 | u8 payload[0]; | ||
1112 | } __attribute__ ((packed)); | ||
1113 | |||
1088 | struct ipw_priv { | 1114 | struct ipw_priv { |
1089 | /* ieee device used by generic ieee processing code */ | 1115 | /* ieee device used by generic ieee processing code */ |
1090 | struct ieee80211_device *ieee; | 1116 | struct ieee80211_device *ieee; |
@@ -1245,6 +1271,8 @@ struct ipw_priv { | |||
1245 | u32 pm_state[16]; | 1271 | u32 pm_state[16]; |
1246 | #endif | 1272 | #endif |
1247 | 1273 | ||
1274 | struct ipw_fw_error *error; | ||
1275 | |||
1248 | /* network state */ | 1276 | /* network state */ |
1249 | 1277 | ||
1250 | /* Used to pass the current INTA value from ISR to Tasklet */ | 1278 | /* Used to pass the current INTA value from ISR to Tasklet */ |
@@ -1803,7 +1831,7 @@ enum { | |||
1803 | IPW_ORD_TABLE_7_LAST | 1831 | IPW_ORD_TABLE_7_LAST |
1804 | }; | 1832 | }; |
1805 | 1833 | ||
1806 | #define IPWSTATUS_ERROR_LOG (IPW_SHARED_LOWER_BOUND + 0x410) | 1834 | #define IPW_ERROR_LOG (IPW_SHARED_LOWER_BOUND + 0x410) |
1807 | #define IPW_EVENT_LOG (IPW_SHARED_LOWER_BOUND + 0x414) | 1835 | #define IPW_EVENT_LOG (IPW_SHARED_LOWER_BOUND + 0x414) |
1808 | #define IPW_ORDINALS_TABLE_LOWER (IPW_SHARED_LOWER_BOUND + 0x500) | 1836 | #define IPW_ORDINALS_TABLE_LOWER (IPW_SHARED_LOWER_BOUND + 0x500) |
1809 | #define IPW_ORDINALS_TABLE_0 (IPW_SHARED_LOWER_BOUND + 0x180) | 1837 | #define IPW_ORDINALS_TABLE_0 (IPW_SHARED_LOWER_BOUND + 0x180) |