Commit b39860c6 authored by James Ketrenos's avatar James Ketrenos

Switched firmware error dumping so that it will capture a log available

via sysfs even if debugging disabled.  When a firmware error is
captured, it will be dumped to the kernel log (if debug enabled) and
captured in memory to be retrieved via sysfs.

If an error has already been captured, subsequent errors will be
dropped.

The existing error can be cleared by writing to the error log entry.
Signed-off-by: default avatarJames Ketrenos <jketreno@linux.intel.com>
parent e666619e
...@@ -428,6 +428,7 @@ static inline void ipw_disable_interrupts(struct ipw_priv *priv) ...@@ -428,6 +428,7 @@ static inline void ipw_disable_interrupts(struct ipw_priv *priv)
ipw_write32(priv, IPW_INTA_MASK_R, ~IPW_INTA_MASK_ALL); ipw_write32(priv, IPW_INTA_MASK_R, ~IPW_INTA_MASK_ALL);
} }
#ifdef CONFIG_IPW_DEBUG
static char *ipw_error_desc(u32 val) static char *ipw_error_desc(u32 val)
{ {
switch (val) { switch (val) {
...@@ -466,56 +467,35 @@ static char *ipw_error_desc(u32 val) ...@@ -466,56 +467,35 @@ static char *ipw_error_desc(u32 val)
} }
} }
static void ipw_dump_nic_error_log(struct ipw_priv *priv) static void ipw_dump_error_log(struct ipw_priv *priv,
struct ipw_fw_error *error)
{ {
u32 desc, time, blink1, blink2, ilink1, ilink2, idata, i, count, base; u32 i;
base = ipw_read32(priv, IPWSTATUS_ERROR_LOG); if (!error) {
count = ipw_read_reg32(priv, base); IPW_ERROR("Error allocating and capturing error log. "
"Nothing to dump.\n");
return;
}
if (ERROR_START_OFFSET <= count * ERROR_ELEM_SIZE) {
IPW_ERROR("Start IPW Error Log Dump:\n"); IPW_ERROR("Start IPW Error Log Dump:\n");
IPW_ERROR("Status: 0x%08X, Config: %08X\n", IPW_ERROR("Status: 0x%08X, Config: %08X\n",
priv->status, priv->config); error->status, error->config);
}
for (i = ERROR_START_OFFSET;
i <= count * ERROR_ELEM_SIZE; i += ERROR_ELEM_SIZE) {
desc = ipw_read_reg32(priv, base + i);
time = ipw_read_reg32(priv, base + i + 1 * sizeof(u32));
blink1 = ipw_read_reg32(priv, base + i + 2 * sizeof(u32));
blink2 = ipw_read_reg32(priv, base + i + 3 * sizeof(u32));
ilink1 = ipw_read_reg32(priv, base + i + 4 * sizeof(u32));
ilink2 = ipw_read_reg32(priv, base + i + 5 * sizeof(u32));
idata = ipw_read_reg32(priv, base + i + 6 * sizeof(u32));
for (i = 0; i < error->elem_len; i++)
IPW_ERROR("%s %i 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", IPW_ERROR("%s %i 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
ipw_error_desc(desc), time, blink1, blink2, ipw_error_desc(error->elem[i].desc),
ilink1, ilink2, idata); error->elem[i].time,
} error->elem[i].blink1,
error->elem[i].blink2,
error->elem[i].link1,
error->elem[i].link2, error->elem[i].data);
for (i = 0; i < error->log_len; i++)
IPW_ERROR("%i\t0x%08x\t%i\n",
error->log[i].time,
error->log[i].event, error->log[i].data);
} }
static void ipw_dump_nic_event_log(struct ipw_priv *priv)
{
u32 ev, time, data, i, count, base;
base = ipw_read32(priv, IPW_EVENT_LOG);
count = ipw_read_reg32(priv, base);
if (EVENT_START_OFFSET <= count * EVENT_ELEM_SIZE)
IPW_ERROR("Start IPW Event Log Dump:\n");
for (i = EVENT_START_OFFSET;
i <= count * EVENT_ELEM_SIZE; i += EVENT_ELEM_SIZE) {
ev = ipw_read_reg32(priv, base + i);
time = ipw_read_reg32(priv, base + i + 1 * sizeof(u32));
data = ipw_read_reg32(priv, base + i + 2 * sizeof(u32));
#ifdef CONFIG_IPW_DEBUG
IPW_ERROR("%i\t0x%08x\t%i\n", time, data, ev);
#endif #endif
}
}
static inline int ipw_is_init(struct ipw_priv *priv) static inline int ipw_is_init(struct ipw_priv *priv)
{ {
...@@ -1058,6 +1038,130 @@ static ssize_t store_debug_level(struct device_driver *d, const char *buf, ...@@ -1058,6 +1038,130 @@ static ssize_t store_debug_level(struct device_driver *d, const char *buf,
static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
show_debug_level, store_debug_level); show_debug_level, store_debug_level);
static inline u32 ipw_get_event_log_len(struct ipw_priv *priv)
{
return ipw_read_reg32(priv, ipw_read32(priv, IPW_EVENT_LOG));
}
static void ipw_capture_event_log(struct ipw_priv *priv,
u32 log_len, struct ipw_event *log)
{
u32 base;
if (log_len) {
base = ipw_read32(priv, IPW_EVENT_LOG);
ipw_read_indirect(priv, base + sizeof(base) + sizeof(u32),
(u8 *) log, sizeof(*log) * log_len);
}
}
static struct ipw_fw_error *ipw_alloc_error_log(struct ipw_priv *priv)
{
struct ipw_fw_error *error;
u32 log_len = ipw_get_event_log_len(priv);
u32 base = ipw_read32(priv, IPW_ERROR_LOG);
u32 elem_len = ipw_read_reg32(priv, base);
error = kmalloc(sizeof(*error) +
sizeof(*error->elem) * elem_len +
sizeof(*error->log) * log_len, GFP_ATOMIC);
if (!error) {
IPW_ERROR("Memory allocation for firmware error log "
"failed.\n");
return NULL;
}
error->status = priv->status;
error->config = priv->config;
error->elem_len = elem_len;
error->log_len = log_len;
error->elem = (struct ipw_error_elem *)error->payload;
error->log = (struct ipw_event *)(error->elem +
(sizeof(*error->elem) * elem_len));
ipw_capture_event_log(priv, log_len, error->log);
if (elem_len)
ipw_read_indirect(priv, base + sizeof(base), (u8 *) error->elem,
sizeof(*error->elem) * elem_len);
return error;
}
static void ipw_free_error_log(struct ipw_fw_error *error)
{
if (error)
kfree(error);
}
static ssize_t show_event_log(struct device *d,
struct device_attribute *attr, char *buf)
{
struct ipw_priv *priv = dev_get_drvdata(d);
u32 log_len = ipw_get_event_log_len(priv);
struct ipw_event log[log_len];
u32 len = 0, i;
ipw_capture_event_log(priv, log_len, log);
len += snprintf(buf + len, PAGE_SIZE - len, "%08X", log_len);
for (i = 0; i < log_len; i++)
len += snprintf(buf + len, PAGE_SIZE - len,
"\n%08X%08X%08X",
log[i].time, log[i].event, log[i].data);
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
return len;
}
static DEVICE_ATTR(event_log, S_IRUGO, show_event_log, NULL);
static ssize_t show_error(struct device *d,
struct device_attribute *attr, char *buf)
{
struct ipw_priv *priv = dev_get_drvdata(d);
u32 len = 0, i;
if (!priv->error)
return 0;
len += snprintf(buf + len, PAGE_SIZE - len,
"%08X%08X%08X",
priv->error->status,
priv->error->config, priv->error->elem_len);
for (i = 0; i < priv->error->elem_len; i++)
len += snprintf(buf + len, PAGE_SIZE - len,
"\n%08X%08X%08X%08X%08X%08X%08X",
priv->error->elem[i].time,
priv->error->elem[i].desc,
priv->error->elem[i].blink1,
priv->error->elem[i].blink2,
priv->error->elem[i].link1,
priv->error->elem[i].link2,
priv->error->elem[i].data);
len += snprintf(buf + len, PAGE_SIZE - len,
"\n%08X", priv->error->log_len);
for (i = 0; i < priv->error->log_len; i++)
len += snprintf(buf + len, PAGE_SIZE - len,
"\n%08X%08X%08X",
priv->error->log[i].time,
priv->error->log[i].event,
priv->error->log[i].data);
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
return len;
}
static ssize_t clear_error(struct device *d,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ipw_priv *priv = dev_get_drvdata(d);
if (priv->error) {
ipw_free_error_log(priv->error);
priv->error = NULL;
}
return count;
}
static DEVICE_ATTR(error, S_IRUGO | S_IWUSR, show_error, clear_error);
static ssize_t show_scan_age(struct device *d, struct device_attribute *attr, static ssize_t show_scan_age(struct device *d, struct device_attribute *attr,
char *buf) char *buf)
{ {
...@@ -1163,34 +1267,6 @@ static ssize_t show_nic_type(struct device *d, ...@@ -1163,34 +1267,6 @@ static ssize_t show_nic_type(struct device *d,
static DEVICE_ATTR(nic_type, S_IRUGO, show_nic_type, NULL); static DEVICE_ATTR(nic_type, S_IRUGO, show_nic_type, NULL);
static ssize_t dump_error_log(struct device *d,
struct device_attribute *attr, const char *buf,
size_t count)
{
char *p = (char *)buf;
if (p[0] == '1')
ipw_dump_nic_error_log((struct ipw_priv *)d->driver_data);
return strnlen(buf, count);
}
static DEVICE_ATTR(dump_errors, S_IWUSR, NULL, dump_error_log);
static ssize_t dump_event_log(struct device *d,
struct device_attribute *attr, const char *buf,
size_t count)
{
char *p = (char *)buf;
if (p[0] == '1')
ipw_dump_nic_event_log((struct ipw_priv *)d->driver_data);
return strnlen(buf, count);
}
static DEVICE_ATTR(dump_events, S_IWUSR, NULL, dump_event_log);
static ssize_t show_ucode_version(struct device *d, static ssize_t show_ucode_version(struct device *d,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
...@@ -1614,12 +1690,30 @@ static void ipw_irq_tasklet(struct ipw_priv *priv) ...@@ -1614,12 +1690,30 @@ static void ipw_irq_tasklet(struct ipw_priv *priv)
if (inta & IPW_INTA_BIT_FATAL_ERROR) { if (inta & IPW_INTA_BIT_FATAL_ERROR) {
IPW_ERROR("Firmware error detected. Restarting.\n"); IPW_ERROR("Firmware error detected. Restarting.\n");
if (priv->error) {
IPW_ERROR("Sysfs 'error' log already exists.\n");
#ifdef CONFIG_IPW_DEBUG #ifdef CONFIG_IPW_DEBUG
if (ipw_debug_level & IPW_DL_FW_ERRORS) { if (ipw_debug_level & IPW_DL_FW_ERRORS) {
ipw_dump_nic_error_log(priv); struct ipw_fw_error *error =
ipw_dump_nic_event_log(priv); ipw_alloc_error_log(priv);
ipw_dump_error_log(priv, error);
if (error)
ipw_free_error_log(error);
} }
#endif #endif
} else {
priv->error = ipw_alloc_error_log(priv);
if (priv->error)
IPW_ERROR("Sysfs 'error' log captured.\n");
else
IPW_ERROR("Error allocating sysfs 'error' "
"log.\n");
#ifdef CONFIG_IPW_DEBUG
if (ipw_debug_level & IPW_DL_FW_ERRORS)
ipw_dump_error_log(priv, priv->error);
#endif
}
/* XXX: If hardware encryption is for WPA/WPA2, /* XXX: If hardware encryption is for WPA/WPA2,
* we have to notify the supplicant. */ * we have to notify the supplicant. */
if (priv->ieee->sec.encrypt) { if (priv->ieee->sec.encrypt) {
...@@ -10958,8 +11052,8 @@ static struct attribute *ipw_sysfs_entries[] = { ...@@ -10958,8 +11052,8 @@ static struct attribute *ipw_sysfs_entries[] = {
&dev_attr_nic_type.attr, &dev_attr_nic_type.attr,
&dev_attr_status.attr, &dev_attr_status.attr,
&dev_attr_cfg.attr, &dev_attr_cfg.attr,
&dev_attr_dump_errors.attr, &dev_attr_error.attr,
&dev_attr_dump_events.attr, &dev_attr_event_log.attr,
&dev_attr_eeprom_delay.attr, &dev_attr_eeprom_delay.attr,
&dev_attr_ucode_version.attr, &dev_attr_ucode_version.attr,
&dev_attr_rtc.attr, &dev_attr_rtc.attr,
...@@ -11172,6 +11266,11 @@ static void ipw_pci_remove(struct pci_dev *pdev) ...@@ -11172,6 +11266,11 @@ static void ipw_pci_remove(struct pci_dev *pdev)
} }
} }
if (priv->error) {
ipw_free_error_log(priv->error);
priv->error = NULL;
}
free_irq(pdev->irq, priv); free_irq(pdev->irq, priv);
iounmap(priv->hw_base); iounmap(priv->hw_base);
pci_release_regions(pdev); pci_release_regions(pdev);
......
...@@ -1085,6 +1085,32 @@ struct ipw_ibss_seq { ...@@ -1085,6 +1085,32 @@ struct ipw_ibss_seq {
struct list_head list; struct list_head list;
}; };
struct ipw_error_elem {
u32 desc;
u32 time;
u32 blink1;
u32 blink2;
u32 link1;
u32 link2;
u32 data;
};
struct ipw_event {
u32 event;
u32 time;
u32 data;
} __attribute__ ((packed));
struct ipw_fw_error {
u32 status;
u32 config;
u32 elem_len;
u32 log_len;
struct ipw_error_elem *elem;
struct ipw_event *log;
u8 payload[0];
} __attribute__ ((packed));
struct ipw_priv { struct ipw_priv {
/* ieee device used by generic ieee processing code */ /* ieee device used by generic ieee processing code */
struct ieee80211_device *ieee; struct ieee80211_device *ieee;
...@@ -1245,6 +1271,8 @@ struct ipw_priv { ...@@ -1245,6 +1271,8 @@ struct ipw_priv {
u32 pm_state[16]; u32 pm_state[16];
#endif #endif
struct ipw_fw_error *error;
/* network state */ /* network state */
/* Used to pass the current INTA value from ISR to Tasklet */ /* Used to pass the current INTA value from ISR to Tasklet */
...@@ -1803,7 +1831,7 @@ enum { ...@@ -1803,7 +1831,7 @@ enum {
IPW_ORD_TABLE_7_LAST IPW_ORD_TABLE_7_LAST
}; };
#define IPWSTATUS_ERROR_LOG (IPW_SHARED_LOWER_BOUND + 0x410) #define IPW_ERROR_LOG (IPW_SHARED_LOWER_BOUND + 0x410)
#define IPW_EVENT_LOG (IPW_SHARED_LOWER_BOUND + 0x414) #define IPW_EVENT_LOG (IPW_SHARED_LOWER_BOUND + 0x414)
#define IPW_ORDINALS_TABLE_LOWER (IPW_SHARED_LOWER_BOUND + 0x500) #define IPW_ORDINALS_TABLE_LOWER (IPW_SHARED_LOWER_BOUND + 0x500)
#define IPW_ORDINALS_TABLE_0 (IPW_SHARED_LOWER_BOUND + 0x180) #define IPW_ORDINALS_TABLE_0 (IPW_SHARED_LOWER_BOUND + 0x180)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment