Skip to content

Commit

Permalink
Switched firmware error dumping so that it will capture a log available
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
James Ketrenos committed Nov 7, 2005
1 parent e666619 commit b39860c
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 77 deletions.
251 changes: 175 additions & 76 deletions drivers/net/wireless/ipw2200.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

base = ipw_read32(priv, IPWSTATUS_ERROR_LOG);
count = ipw_read_reg32(priv, base);
u32 i;

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)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
30 changes: 29 additions & 1 deletion drivers/net/wireless/ipw2200.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit b39860c

Please sign in to comment.