mirror of
https://github.com/torvalds/linux.git
synced 2024-11-17 17:41:44 +00:00
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: James Ketrenos <jketreno@linux.intel.com>
This commit is contained in:
parent
e666619e23
commit
b39860c60b
@ -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);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IPW_DEBUG
|
||||
static char *ipw_error_desc(u32 val)
|
||||
{
|
||||
switch (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);
|
||||
count = ipw_read_reg32(priv, base);
|
||||
|
||||
if (ERROR_START_OFFSET <= count * ERROR_ELEM_SIZE) {
|
||||
IPW_ERROR("Start IPW Error Log Dump:\n");
|
||||
IPW_ERROR("Status: 0x%08X, Config: %08X\n",
|
||||
priv->status, priv->config);
|
||||
if (!error) {
|
||||
IPW_ERROR("Error allocating and capturing error log. "
|
||||
"Nothing to dump.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
IPW_ERROR("Start IPW Error Log Dump:\n");
|
||||
IPW_ERROR("Status: 0x%08X, Config: %08X\n",
|
||||
error->status, error->config);
|
||||
|
||||
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_desc(desc), time, blink1, blink2,
|
||||
ilink1, ilink2, idata);
|
||||
}
|
||||
ipw_error_desc(error->elem[i].desc),
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
|
||||
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,
|
||||
char *buf)
|
||||
{
|
||||
@ -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 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,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
@ -1614,12 +1690,30 @@ static void ipw_irq_tasklet(struct ipw_priv *priv)
|
||||
|
||||
if (inta & IPW_INTA_BIT_FATAL_ERROR) {
|
||||
IPW_ERROR("Firmware error detected. Restarting.\n");
|
||||
if (priv->error) {
|
||||
IPW_ERROR("Sysfs 'error' log already exists.\n");
|
||||
#ifdef CONFIG_IPW_DEBUG
|
||||
if (ipw_debug_level & IPW_DL_FW_ERRORS) {
|
||||
ipw_dump_nic_error_log(priv);
|
||||
ipw_dump_nic_event_log(priv);
|
||||
}
|
||||
if (ipw_debug_level & IPW_DL_FW_ERRORS) {
|
||||
struct ipw_fw_error *error =
|
||||
ipw_alloc_error_log(priv);
|
||||
ipw_dump_error_log(priv, error);
|
||||
if (error)
|
||||
ipw_free_error_log(error);
|
||||
}
|
||||
#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,
|
||||
* we have to notify the supplicant. */
|
||||
if (priv->ieee->sec.encrypt) {
|
||||
@ -10958,8 +11052,8 @@ static struct attribute *ipw_sysfs_entries[] = {
|
||||
&dev_attr_nic_type.attr,
|
||||
&dev_attr_status.attr,
|
||||
&dev_attr_cfg.attr,
|
||||
&dev_attr_dump_errors.attr,
|
||||
&dev_attr_dump_events.attr,
|
||||
&dev_attr_error.attr,
|
||||
&dev_attr_event_log.attr,
|
||||
&dev_attr_eeprom_delay.attr,
|
||||
&dev_attr_ucode_version.attr,
|
||||
&dev_attr_rtc.attr,
|
||||
@ -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);
|
||||
iounmap(priv->hw_base);
|
||||
pci_release_regions(pdev);
|
||||
|
@ -1085,6 +1085,32 @@ struct ipw_ibss_seq {
|
||||
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 {
|
||||
/* ieee device used by generic ieee processing code */
|
||||
struct ieee80211_device *ieee;
|
||||
@ -1245,6 +1271,8 @@ struct ipw_priv {
|
||||
u32 pm_state[16];
|
||||
#endif
|
||||
|
||||
struct ipw_fw_error *error;
|
||||
|
||||
/* network state */
|
||||
|
||||
/* Used to pass the current INTA value from ISR to Tasklet */
|
||||
@ -1803,7 +1831,7 @@ enum {
|
||||
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_ORDINALS_TABLE_LOWER (IPW_SHARED_LOWER_BOUND + 0x500)
|
||||
#define IPW_ORDINALS_TABLE_0 (IPW_SHARED_LOWER_BOUND + 0x180)
|
||||
|
Loading…
Reference in New Issue
Block a user