Firmware sends binary logs with WMIX_DBGLOG_EVENTID event. Create
a buffer which stores the latest logs and which can be copied from
fwlog debugfs file with cp command.
To save memory firmware log support is enabled only when CONFIG_ATH6KL_DEBUG
is enabled.
Signed-off-by: Kalle Valo <[email protected]>
---
drivers/net/wireless/ath/ath6kl/core.h | 10 ++
drivers/net/wireless/ath/ath6kl/debug.c | 154 ++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath6kl/debug.h | 13 +++
drivers/net/wireless/ath/ath6kl/init.c | 2
drivers/net/wireless/ath/ath6kl/target.h | 3 +
drivers/net/wireless/ath/ath6kl/wmi.c | 1
6 files changed, 182 insertions(+), 1 deletions(-)
diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h
index c5537b3..89ca343 100644
--- a/drivers/net/wireless/ath/ath6kl/core.h
+++ b/drivers/net/wireless/ath/ath6kl/core.h
@@ -21,6 +21,7 @@
#include <linux/rtnetlink.h>
#include <linux/firmware.h>
#include <linux/sched.h>
+#include <linux/circ_buf.h>
#include <net/cfg80211.h>
#include "htc.h"
#include "wmi.h"
@@ -468,6 +469,15 @@ struct ath6kl {
struct ath6kl_node_table scan_table;
struct dentry *debugfs_phy;
+
+#ifdef CONFIG_ATH6KL_DEBUG
+ struct {
+ struct circ_buf fwlog_buf;
+ spinlock_t fwlog_lock;
+ void *fwlog_tmp;
+ } debug;
+#endif /* CONFIG_ATH6KL_DEBUG */
+
};
static inline void *ath6kl_priv(struct net_device *dev)
diff --git a/drivers/net/wireless/ath/ath6kl/debug.c b/drivers/net/wireless/ath/ath6kl/debug.c
index 2b46287..b2706da 100644
--- a/drivers/net/wireless/ath/ath6kl/debug.c
+++ b/drivers/net/wireless/ath/ath6kl/debug.c
@@ -15,7 +15,23 @@
*/
#include "core.h"
+
+#include <linux/circ_buf.h>
+
#include "debug.h"
+#include "target.h"
+
+struct ath6kl_fwlog_slot {
+ __le32 timestamp;
+ __le32 length;
+
+ /* max ATH6KL_FWLOG_PAYLOAD_SIZE bytes */
+ u8 payload[0];
+};
+
+#define ATH6KL_FWLOG_SIZE 32768
+#define ATH6KL_FWLOG_SLOT_SIZE (sizeof(struct ath6kl_fwlog_slot) + \
+ ATH6KL_FWLOG_PAYLOAD_SIZE)
int ath6kl_printk(const char *level, const char *fmt, ...)
{
@@ -153,6 +169,117 @@ static int ath6kl_debugfs_open(struct inode *inode, struct file *file)
return 0;
}
+static void ath6kl_debug_fwlog_add(struct ath6kl *ar, const void *buf,
+ size_t buf_len)
+{
+ struct circ_buf *fwlog = &ar->debug.fwlog_buf;
+ size_t space;
+ int i;
+
+ /* entries must all be equal size */
+ if (WARN_ON(buf_len != ATH6KL_FWLOG_SLOT_SIZE))
+ return;
+
+ space = CIRC_SPACE(fwlog->head, fwlog->tail, ATH6KL_FWLOG_SIZE);
+ if (space < buf_len)
+ /* discard oldest slot */
+ fwlog->tail = (fwlog->tail + ATH6KL_FWLOG_SLOT_SIZE) &
+ (ATH6KL_FWLOG_SIZE - 1);
+
+ for (i = 0; i < buf_len; i += space) {
+ space = CIRC_SPACE_TO_END(fwlog->head, fwlog->tail,
+ ATH6KL_FWLOG_SIZE);
+
+ if ((size_t) space > buf_len - i)
+ space = buf_len - i;
+
+ memcpy(&fwlog->buf[fwlog->head], buf, space);
+ fwlog->head = (fwlog->head + space) & (ATH6KL_FWLOG_SIZE - 1);
+ }
+
+}
+
+void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len)
+{
+ struct ath6kl_fwlog_slot *slot = ar->debug.fwlog_tmp;
+ size_t slot_len;
+
+ if (WARN_ON(len > ATH6KL_FWLOG_PAYLOAD_SIZE))
+ return;
+
+ spin_lock_bh(&ar->debug.fwlog_lock);
+
+ slot->timestamp = cpu_to_le32(jiffies);
+ slot->length = cpu_to_le32(len);
+ memcpy(slot->payload, buf, len);
+
+ slot_len = sizeof(*slot) + len;
+
+ if (slot_len < ATH6KL_FWLOG_SLOT_SIZE)
+ memset(slot->payload + len, 0,
+ ATH6KL_FWLOG_SLOT_SIZE - slot_len);
+
+ ath6kl_debug_fwlog_add(ar, slot, ATH6KL_FWLOG_SLOT_SIZE);
+
+ spin_unlock_bh(&ar->debug.fwlog_lock);
+}
+
+static bool ath6kl_debug_fwlog_empty(struct ath6kl *ar)
+{
+ return CIRC_CNT(ar->debug.fwlog_buf.head,
+ ar->debug.fwlog_buf.tail,
+ ATH6KL_FWLOG_SLOT_SIZE) == 0;
+}
+
+static ssize_t ath6kl_fwlog_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct ath6kl *ar = file->private_data;
+ struct circ_buf *fwlog = &ar->debug.fwlog_buf;
+ size_t len = 0, buf_len = count;
+ ssize_t ret_cnt;
+ char *buf;
+ int ccnt;
+
+ buf = vmalloc(buf_len);
+ if (!buf)
+ return -ENOMEM;
+
+ spin_lock_bh(&ar->debug.fwlog_lock);
+
+ while (len < buf_len && !ath6kl_debug_fwlog_empty(ar)) {
+ ccnt = CIRC_CNT_TO_END(fwlog->head, fwlog->tail,
+ ATH6KL_FWLOG_SIZE);
+
+ if ((size_t) ccnt > buf_len - len)
+ ccnt = buf_len - len;
+
+ memcpy(buf + len, &fwlog->buf[fwlog->tail], ccnt);
+ len += ccnt;
+
+ fwlog->tail = (fwlog->tail + ccnt) &
+ (ATH6KL_FWLOG_SIZE - 1);
+ }
+
+ spin_unlock_bh(&ar->debug.fwlog_lock);
+
+ if (WARN_ON(len > buf_len))
+ len = buf_len;
+
+ ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+ vfree(buf);
+
+ return ret_cnt;
+}
+
+static const struct file_operations fops_fwlog = {
+ .open = ath6kl_debugfs_open,
+ .read = ath6kl_fwlog_read,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+
static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
@@ -358,10 +485,25 @@ static const struct file_operations fops_credit_dist_stats = {
int ath6kl_debug_init(struct ath6kl *ar)
{
+ ar->debug.fwlog_buf.buf = vmalloc(ATH6KL_FWLOG_SIZE);
+ if (ar->debug.fwlog_buf.buf == NULL)
+ return -ENOMEM;
+
+ ar->debug.fwlog_tmp = kmalloc(ATH6KL_FWLOG_SLOT_SIZE, GFP_KERNEL);
+ if (ar->debug.fwlog_tmp == NULL) {
+ vfree(ar->debug.fwlog_buf.buf);
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&ar->debug.fwlog_lock);
+
ar->debugfs_phy = debugfs_create_dir("ath6kl",
ar->wdev->wiphy->debugfsdir);
- if (!ar->debugfs_phy)
+ if (!ar->debugfs_phy) {
+ vfree(ar->debug.fwlog_buf.buf);
+ kfree(ar->debug.fwlog_tmp);
return -ENOMEM;
+ }
debugfs_create_file("tgt_stats", S_IRUSR, ar->debugfs_phy, ar,
&fops_tgt_stats);
@@ -369,6 +511,16 @@ int ath6kl_debug_init(struct ath6kl *ar)
debugfs_create_file("credit_dist_stats", S_IRUSR, ar->debugfs_phy, ar,
&fops_credit_dist_stats);
+ debugfs_create_file("fwlog", S_IRUSR, ar->debugfs_phy, ar,
+ &fops_fwlog);
+
return 0;
}
+
+void ath6kl_debug_cleanup(struct ath6kl *ar)
+{
+ vfree(ar->debug.fwlog_buf.buf);
+ kfree(ar->debug.fwlog_tmp);
+}
+
#endif
diff --git a/drivers/net/wireless/ath/ath6kl/debug.h b/drivers/net/wireless/ath/ath6kl/debug.h
index e8c9ea9..f0d6471 100644
--- a/drivers/net/wireless/ath/ath6kl/debug.h
+++ b/drivers/net/wireless/ath/ath6kl/debug.h
@@ -78,7 +78,10 @@ void ath6kl_dump_registers(struct ath6kl_device *dev,
struct ath6kl_irq_proc_registers *irq_proc_reg,
struct ath6kl_irq_enable_reg *irq_en_reg);
void dump_cred_dist_stats(struct htc_target *target);
+void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len);
int ath6kl_debug_init(struct ath6kl *ar);
+void ath6kl_debug_cleanup(struct ath6kl *ar);
+
#else
static inline int ath6kl_dbg(enum ATH6K_DEBUG_MASK dbg_mask,
const char *fmt, ...)
@@ -101,9 +104,19 @@ static inline void ath6kl_dump_registers(struct ath6kl_device *dev,
static inline void dump_cred_dist_stats(struct htc_target *target)
{
}
+
+void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len)
+{
+}
+
static inline int ath6kl_debug_init(struct ath6kl *ar)
{
return 0;
}
+
+void ath6kl_debug_cleanup(struct ath6kl *ar)
+{
+}
+
#endif
#endif
diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c
index ad9716c..bcb94ab 100644
--- a/drivers/net/wireless/ath/ath6kl/init.c
+++ b/drivers/net/wireless/ath/ath6kl/init.c
@@ -1353,6 +1353,8 @@ void ath6kl_destroy(struct net_device *dev, unsigned int unregister)
ath6kl_bmi_cleanup(ar);
+ ath6kl_debug_cleanup(ar);
+
if (unregister && test_bit(NETDEV_REGISTERED, &ar->flag)) {
unregister_netdev(dev);
clear_bit(NETDEV_REGISTERED, &ar->flag);
diff --git a/drivers/net/wireless/ath/ath6kl/target.h b/drivers/net/wireless/ath/ath6kl/target.h
index 53e2c78..6c66a08 100644
--- a/drivers/net/wireless/ath/ath6kl/target.h
+++ b/drivers/net/wireless/ath/ath6kl/target.h
@@ -340,4 +340,7 @@ struct host_interest {
#define AR6004_REV1_BOARD_DATA_ADDRESS 0x435400
#define AR6004_REV1_BOARD_EXT_DATA_ADDRESS 0x437000
#define AR6004_REV1_RAM_RESERVE_SIZE 11264
+
+#define ATH6KL_FWLOG_PAYLOAD_SIZE 1500
+
#endif
diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c
index d116d0e..b96a734 100644
--- a/drivers/net/wireless/ath/ath6kl/wmi.c
+++ b/drivers/net/wireless/ath/ath6kl/wmi.c
@@ -2482,6 +2482,7 @@ static int ath6kl_wmi_control_rx_xtnd(struct wmi *wmi, struct sk_buff *skb)
case WMIX_HB_CHALLENGE_RESP_EVENTID:
break;
case WMIX_DBGLOG_EVENTID:
+ ath6kl_debug_fwlog_event(wmi->parent_dev, datap, len);
break;
default:
ath6kl_err("unknown cmd id 0x%x\n", id);
Just to make them a bit easier to read and unify naming. 32 suffix
in the function name means that it will be a 32 bit transfer. If there's
no number a buffer is transfered instead.
Use void pointers to get rid of ugly casts.
Don't provide target address as a pointer, pass it by value. Same for
the value used in write32().
Signed-off-by: Kalle Valo <[email protected]>
---
drivers/net/wireless/ath/ath6kl/core.h | 6 +-
drivers/net/wireless/ath/ath6kl/init.c | 16 ++---
drivers/net/wireless/ath/ath6kl/main.c | 94 ++++++++++++++++++--------------
3 files changed, 61 insertions(+), 55 deletions(-)
diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h
index d5c21bf..33c84c0 100644
--- a/drivers/net/wireless/ath/ath6kl/core.h
+++ b/drivers/net/wireless/ath/ath6kl/core.h
@@ -509,9 +509,9 @@ enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target,
struct htc_packet *packet);
void ath6kl_stop_txrx(struct ath6kl *ar);
void ath6kl_cleanup_amsdu_rxbufs(struct ath6kl *ar);
-int ath6kl_access_datadiag(struct ath6kl *ar, u32 address,
- u8 *data, u32 length, bool read);
-int ath6kl_read_reg_diag(struct ath6kl *ar, u32 *address, u32 *data);
+int ath6kl_diag_write(struct ath6kl *ar, u32 address, void *data, u32 length);
+int ath6kl_diag_read32(struct ath6kl *ar, u32 address, u32 *value);
+int ath6kl_diag_read(struct ath6kl *ar, u32 address, void *data, u32 length);
void ath6kl_init_profile_info(struct ath6kl *ar);
void ath6kl_tx_data_cleanup(struct ath6kl *ar);
void ath6kl_stop_endpoint(struct net_device *dev, bool keep_profile,
diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c
index bcb94ab..b6a1e69 100644
--- a/drivers/net/wireless/ath/ath6kl/init.c
+++ b/drivers/net/wireless/ath/ath6kl/init.c
@@ -131,14 +131,13 @@ static int ath6kl_set_host_app_area(struct ath6kl *ar)
address = ath6kl_get_hi_item_addr(ar, HI_ITEM(hi_app_host_interest));
address = TARG_VTOP(ar->target_type, address);
- if (ath6kl_read_reg_diag(ar, &address, &data))
+ if (ath6kl_diag_read32(ar, address, &data))
return -EIO;
address = TARG_VTOP(ar->target_type, data);
host_app_area.wmi_protocol_ver = WMI_PROTOCOL_VERSION;
- if (ath6kl_access_datadiag(ar, address,
- (u8 *)&host_app_area,
- sizeof(struct host_app_area), false))
+ if (ath6kl_diag_write(ar, address, (u8 *) &host_app_area,
+ sizeof(struct host_app_area)))
return -EIO;
return 0;
@@ -375,7 +374,7 @@ static void ath6kl_dump_target_assert_info(struct ath6kl *ar)
address = TARG_VTOP(ar->target_type, address);
/* read RAM location through diagnostic window */
- status = ath6kl_read_reg_diag(ar, &address, ®dump_loc);
+ status = ath6kl_diag_read32(ar, address, ®dump_loc);
if (status || !regdump_loc) {
ath6kl_err("failed to get ptr to register dump area\n");
@@ -387,11 +386,8 @@ static void ath6kl_dump_target_assert_info(struct ath6kl *ar)
regdump_loc = TARG_VTOP(ar->target_type, regdump_loc);
/* fetch register dump data */
- status = ath6kl_access_datadiag(ar,
- regdump_loc,
- (u8 *)®dump_val[0],
- REG_DUMP_COUNT_AR6003 * (sizeof(u32)),
- true);
+ status = ath6kl_diag_read(ar, regdump_loc, (u8 *)®dump_val[0],
+ REG_DUMP_COUNT_AR6003 * (sizeof(u32)));
if (status) {
ath6kl_err("failed to get register dump\n");
diff --git a/drivers/net/wireless/ath/ath6kl/main.c b/drivers/net/wireless/ath/ath6kl/main.c
index b64b2a3..8d29d4e 100644
--- a/drivers/net/wireless/ath/ath6kl/main.c
+++ b/drivers/net/wireless/ath/ath6kl/main.c
@@ -228,74 +228,84 @@ static int ath6kl_set_addrwin_reg(struct ath6kl *ar, u32 reg_addr, u32 addr)
}
/*
- * Read from the ATH6KL through its diagnostic window. No cooperation from
- * the Target is required for this.
+ * Read from the hardware through its diagnostic window. No cooperation
+ * from the firmware is required for this.
*/
-int ath6kl_read_reg_diag(struct ath6kl *ar, u32 *address, u32 *data)
+int ath6kl_diag_read32(struct ath6kl *ar, u32 address, u32 *value)
{
- int status;
+ int ret;
/* set window register to start read cycle */
- status = ath6kl_set_addrwin_reg(ar, WINDOW_READ_ADDR_ADDRESS,
- *address);
-
- if (status)
- return status;
+ ret = ath6kl_set_addrwin_reg(ar, WINDOW_READ_ADDR_ADDRESS, address);
+ if (ret)
+ return ret;
/* read the data */
- status = hif_read_write_sync(ar, WINDOW_DATA_ADDRESS, (u8 *)data,
- sizeof(u32), HIF_RD_SYNC_BYTE_INC);
- if (status) {
- ath6kl_err("failed to read from window data addr\n");
- return status;
+ ret = hif_read_write_sync(ar, WINDOW_DATA_ADDRESS, (u8 *) value,
+ sizeof(*value), HIF_RD_SYNC_BYTE_INC);
+ if (ret) {
+ ath6kl_warn("failed to read32 through diagnose window: %d\n",
+ ret);
+ return ret;
}
- return status;
+ return 0;
}
-
/*
* Write to the ATH6KL through its diagnostic window. No cooperation from
* the Target is required for this.
*/
-static int ath6kl_write_reg_diag(struct ath6kl *ar, u32 *address, u32 *data)
+static int ath6kl_diag_write32(struct ath6kl *ar, u32 address, u32 value)
{
- int status;
+ int ret;
/* set write data */
- status = hif_read_write_sync(ar, WINDOW_DATA_ADDRESS, (u8 *)data,
- sizeof(u32), HIF_WR_SYNC_BYTE_INC);
- if (status) {
- ath6kl_err("failed to write 0x%x to window data addr\n", *data);
- return status;
+ ret = hif_read_write_sync(ar, WINDOW_DATA_ADDRESS, (u8 *) &value,
+ sizeof(value), HIF_WR_SYNC_BYTE_INC);
+ if (ret) {
+ ath6kl_err("failed to write 0x%x during diagnose window to 0x%d\n",
+ address, value);
+ return ret;
}
/* set window register, which starts the write cycle */
return ath6kl_set_addrwin_reg(ar, WINDOW_WRITE_ADDR_ADDRESS,
- *address);
+ address);
}
-int ath6kl_access_datadiag(struct ath6kl *ar, u32 address,
- u8 *data, u32 length, bool read)
+int ath6kl_diag_read(struct ath6kl *ar, u32 address, void *data, u32 length)
{
- u32 count;
- int status = 0;
+ u32 count, *buf = data;
+ int ret;
- for (count = 0; count < length; count += 4, address += 4) {
- if (read) {
- status = ath6kl_read_reg_diag(ar, &address,
- (u32 *) &data[count]);
- if (status)
- break;
- } else {
- status = ath6kl_write_reg_diag(ar, &address,
- (u32 *) &data[count]);
- if (status)
- break;
- }
+ if (WARN_ON(length % 4))
+ return -EINVAL;
+
+ for (count = 0; count < length / 4; count++, address += 4) {
+ ret = ath6kl_diag_read32(ar, address, &buf[count]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int ath6kl_diag_write(struct ath6kl *ar, u32 address, void *data, u32 length)
+{
+ u32 count, *buf = data;
+ int ret;
+
+ if (WARN_ON(length % 4))
+ return -EINVAL;
+
+ for (count = 0; count < length / 4; count++, address += 4) {
+ ret = ath6kl_diag_write32(ar, address, buf[count]);
+ if (ret)
+ return ret;
}
- return status;
+ return ret;
}
/* FIXME: move to a better place, target.h? */
@@ -327,7 +337,7 @@ static void ath6kl_reset_device(struct ath6kl *ar, u32 target_type,
break;
}
- status = ath6kl_write_reg_diag(ar, &address, &data);
+ status = ath6kl_diag_write32(ar, address, data);
if (status)
ath6kl_err("failed to reset target\n");
Firmare sends the logs only when it's internal ring buffer is full. But
if firmware crashes we need to retrieve the latest logs through diagnose
window. This is now done everytime the debugfs file is read.
Signed-off-by: Kalle Valo <[email protected]>
---
drivers/net/wireless/ath/ath6kl/core.h | 15 ++++++
drivers/net/wireless/ath/ath6kl/debug.c | 3 +
drivers/net/wireless/ath/ath6kl/init.c | 13 -----
drivers/net/wireless/ath/ath6kl/main.c | 75 ++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath6kl/target.h | 14 ++++++
5 files changed, 107 insertions(+), 13 deletions(-)
diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h
index 33c84c0..61d3142 100644
--- a/drivers/net/wireless/ath/ath6kl/core.h
+++ b/drivers/net/wireless/ath/ath6kl/core.h
@@ -26,6 +26,7 @@
#include "htc.h"
#include "wmi.h"
#include "bmi.h"
+#include "target.h"
#define MAX_ATH6KL 1
#define ATH6KL_MAX_RX_BUFFERS 16
@@ -496,6 +497,19 @@ static inline void ath6kl_deposit_credit_to_ep(struct htc_credit_state_info
cred_info->cur_free_credits -= credits;
}
+static inline u32 ath6kl_get_hi_item_addr(struct ath6kl *ar,
+ u32 item_offset)
+{
+ u32 addr = 0;
+
+ if (ar->target_type == TARGET_TYPE_AR6003)
+ addr = ATH6KL_AR6003_HI_START_ADDR + item_offset;
+ else if (ar->target_type == TARGET_TYPE_AR6004)
+ addr = ATH6KL_AR6004_HI_START_ADDR + item_offset;
+
+ return addr;
+}
+
void ath6kl_destroy(struct net_device *dev, unsigned int unregister);
int ath6kl_configure_target(struct ath6kl *ar);
void ath6kl_detect_error(unsigned long ptr);
@@ -512,6 +526,7 @@ void ath6kl_cleanup_amsdu_rxbufs(struct ath6kl *ar);
int ath6kl_diag_write(struct ath6kl *ar, u32 address, void *data, u32 length);
int ath6kl_diag_read32(struct ath6kl *ar, u32 address, u32 *value);
int ath6kl_diag_read(struct ath6kl *ar, u32 address, void *data, u32 length);
+int ath6kl_read_fwlogs(struct ath6kl *ar);
void ath6kl_init_profile_info(struct ath6kl *ar);
void ath6kl_tx_data_cleanup(struct ath6kl *ar);
void ath6kl_stop_endpoint(struct net_device *dev, bool keep_profile,
diff --git a/drivers/net/wireless/ath/ath6kl/debug.c b/drivers/net/wireless/ath/ath6kl/debug.c
index 80c05f2..9608692 100644
--- a/drivers/net/wireless/ath/ath6kl/debug.c
+++ b/drivers/net/wireless/ath/ath6kl/debug.c
@@ -247,6 +247,9 @@ static ssize_t ath6kl_fwlog_read(struct file *file, char __user *user_buf,
if (!buf)
return -ENOMEM;
+ /* read undelivered logs from firmware */
+ ath6kl_read_fwlogs(ar);
+
spin_lock_bh(&ar->debug.fwlog_lock);
while (len < buf_len && !ath6kl_debug_fwlog_empty(ar)) {
diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c
index b6a1e69..428b9c0 100644
--- a/drivers/net/wireless/ath/ath6kl/init.c
+++ b/drivers/net/wireless/ath/ath6kl/init.c
@@ -108,19 +108,6 @@ static u8 ath6kl_get_fw_iftype(struct ath6kl *ar)
}
}
-static inline u32 ath6kl_get_hi_item_addr(struct ath6kl *ar,
- u32 item_offset)
-{
- u32 addr = 0;
-
- if (ar->target_type == TARGET_TYPE_AR6003)
- addr = ATH6KL_AR6003_HI_START_ADDR + item_offset;
- else if (ar->target_type == TARGET_TYPE_AR6004)
- addr = ATH6KL_AR6004_HI_START_ADDR + item_offset;
-
- return addr;
-}
-
static int ath6kl_set_host_app_area(struct ath6kl *ar)
{
u32 address, data;
diff --git a/drivers/net/wireless/ath/ath6kl/main.c b/drivers/net/wireless/ath/ath6kl/main.c
index 8d29d4e..5e807a9 100644
--- a/drivers/net/wireless/ath/ath6kl/main.c
+++ b/drivers/net/wireless/ath/ath6kl/main.c
@@ -308,6 +308,81 @@ int ath6kl_diag_write(struct ath6kl *ar, u32 address, void *data, u32 length)
return ret;
}
+int ath6kl_read_fwlogs(struct ath6kl *ar)
+{
+ struct ath6kl_dbglog_hdr debug_hdr;
+ struct ath6kl_dbglog_buf debug_buf;
+ u32 address, length, dropped, firstbuf, debug_hdr_addr;
+ int ret = 0, loop;
+ u8 *buf;
+
+ buf = kmalloc(ATH6KL_FWLOG_PAYLOAD_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ address = TARG_VTOP(ar->target_type,
+ ath6kl_get_hi_item_addr(ar,
+ HI_ITEM(hi_dbglog_hdr)));
+
+ ret = ath6kl_diag_read32(ar, address, &debug_hdr_addr);
+ if (ret)
+ goto out;
+
+ /* Get the contents of the ring buffer */
+ if (debug_hdr_addr == 0) {
+ ath6kl_warn("Invalid address for debug_hdr_addr\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ address = TARG_VTOP(ar->target_type, debug_hdr_addr);
+ ath6kl_diag_read(ar, address, &debug_hdr, sizeof(debug_hdr));
+
+ address = TARG_VTOP(ar->target_type,
+ le32_to_cpu(debug_hdr.dbuf_addr));
+ firstbuf = address;
+ dropped = le32_to_cpu(debug_hdr.dropped);
+ ath6kl_diag_read(ar, address, &debug_buf, sizeof(debug_buf));
+
+ loop = 100;
+
+ do {
+ address = TARG_VTOP(ar->target_type,
+ le32_to_cpu(debug_buf.buffer_addr));
+ length = le32_to_cpu(debug_buf.length);
+
+ if (length != 0 && (le32_to_cpu(debug_buf.length) <=
+ le32_to_cpu(debug_buf.bufsize))) {
+ length = ALIGN(length, 4);
+
+ ret = ath6kl_diag_read(ar, address,
+ buf, length);
+ if (ret)
+ goto out;
+
+ ath6kl_debug_fwlog_event(ar, buf, length);
+ }
+
+ address = TARG_VTOP(ar->target_type,
+ le32_to_cpu(debug_buf.next));
+ ath6kl_diag_read(ar, address, &debug_buf, sizeof(debug_buf));
+ if (ret)
+ goto out;
+
+ loop--;
+
+ if (WARN_ON(loop == 0)) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ } while (address != firstbuf);
+
+out:
+ kfree(buf);
+
+ return ret;
+}
+
/* FIXME: move to a better place, target.h? */
#define AR6003_RESET_CONTROL_ADDRESS 0x00004000
#define AR6004_RESET_CONTROL_ADDRESS 0x00004000
diff --git a/drivers/net/wireless/ath/ath6kl/target.h b/drivers/net/wireless/ath/ath6kl/target.h
index 6c66a08..dd8b953 100644
--- a/drivers/net/wireless/ath/ath6kl/target.h
+++ b/drivers/net/wireless/ath/ath6kl/target.h
@@ -343,4 +343,18 @@ struct host_interest {
#define ATH6KL_FWLOG_PAYLOAD_SIZE 1500
+struct ath6kl_dbglog_buf {
+ __le32 next;
+ __le32 buffer_addr;
+ __le32 bufsize;
+ __le32 length;
+ __le32 count;
+ __le32 free;
+} __packed;
+
+struct ath6kl_dbglog_hdr {
+ __le32 dbuf_addr;
+ __le32 dropped;
+} __packed;
+
#endif
On Tue, Aug 30, 2011 at 04:44:50PM +0300, Kalle Valo wrote:
> Just to make them a bit easier to read and unify naming. 32 suffix
> in the function name means that it will be a 32 bit transfer. If there's
> no number a buffer is transfered instead.
>
> Use void pointers to get rid of ugly casts.
>
> Don't provide target address as a pointer, pass it by value. Same for
> the value used in write32().
>
> Signed-off-by: Kalle Valo <[email protected]>
> ---
> drivers/net/wireless/ath/ath6kl/core.h | 6 +-
> drivers/net/wireless/ath/ath6kl/init.c | 16 ++---
> drivers/net/wireless/ath/ath6kl/main.c | 94 ++++++++++++++++++--------------
> 3 files changed, 61 insertions(+), 55 deletions(-)
<--snip-->
> +int ath6kl_diag_write(struct ath6kl *ar, u32 address, void *data, u32 length)
> +{
> + u32 count, *buf = data;
> + int ret;
> +
> + if (WARN_ON(length % 4))
> + return -EINVAL;
> +
> + for (count = 0; count < length / 4; count++, address += 4) {
> + ret = ath6kl_diag_write32(ar, address, buf[count]);
> + if (ret)
> + return ret;
> }
>
> - return status;
> + return ret;
drivers/net/wireless/ath/ath6kl/main.c: In function ‘ath6kl_diag_write’:
drivers/net/wireless/ath/ath6kl/main.c:297: warning: ‘ret’ may be
used uninitialized in this function
Vasanth
> }
>
> /* FIXME: move to a better place, target.h? */
> @@ -327,7 +337,7 @@ static void ath6kl_reset_device(struct ath6kl *ar, u32 target_type,
> break;
> }
>
> - status = ath6kl_write_reg_diag(ar, &address, &data);
> + status = ath6kl_diag_write32(ar, address, data);
>
> if (status)
> ath6kl_err("failed to reset target\n");
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
On 08/30/2011 06:00 PM, Vasanthakumar Thiagarajan wrote:
> On Tue, Aug 30, 2011 at 04:44:50PM +0300, Kalle Valo wrote:
>
>> +int ath6kl_diag_write(struct ath6kl *ar, u32 address, void *data, u32 length)
>> +{
>> + u32 count, *buf = data;
>> + int ret;
>> +
>> + if (WARN_ON(length % 4))
>> + return -EINVAL;
>> +
>> + for (count = 0; count < length / 4; count++, address += 4) {
>> + ret = ath6kl_diag_write32(ar, address, buf[count]);
>> + if (ret)
>> + return ret;
>> }
>>
>> - return status;
>> + return ret;
>
> drivers/net/wireless/ath/ath6kl/main.c: In function ‘ath6kl_diag_write’:
> drivers/net/wireless/ath/ath6kl/main.c:297: warning: ‘ret’ may be
> used uninitialized in this function
Strange, my compiler didn't warn about this. Fixed in v2.
Thanks,
Kalle
Firmware log parameters can be controlled now with help of fwlog_mask
debugfs file.
Signed-off-by: Kalle Valo <[email protected]>
---
drivers/net/wireless/ath/ath6kl/core.h | 1 +
drivers/net/wireless/ath/ath6kl/debug.c | 56 +++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath6kl/wmi.c | 19 +++++++++++
drivers/net/wireless/ath/ath6kl/wmi.h | 6 +++
4 files changed, 82 insertions(+), 0 deletions(-)
diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h
index 89ca343..d5c21bf 100644
--- a/drivers/net/wireless/ath/ath6kl/core.h
+++ b/drivers/net/wireless/ath/ath6kl/core.h
@@ -475,6 +475,7 @@ struct ath6kl {
struct circ_buf fwlog_buf;
spinlock_t fwlog_lock;
void *fwlog_tmp;
+ u32 fwlog_mask;
} debug;
#endif /* CONFIG_ATH6KL_DEBUG */
diff --git a/drivers/net/wireless/ath/ath6kl/debug.c b/drivers/net/wireless/ath/ath6kl/debug.c
index b2706da..80c05f2 100644
--- a/drivers/net/wireless/ath/ath6kl/debug.c
+++ b/drivers/net/wireless/ath/ath6kl/debug.c
@@ -17,6 +17,7 @@
#include "core.h"
#include <linux/circ_buf.h>
+#include <linux/fs.h>
#include "debug.h"
#include "target.h"
@@ -32,6 +33,7 @@ struct ath6kl_fwlog_slot {
#define ATH6KL_FWLOG_SIZE 32768
#define ATH6KL_FWLOG_SLOT_SIZE (sizeof(struct ath6kl_fwlog_slot) + \
ATH6KL_FWLOG_PAYLOAD_SIZE)
+#define ATH6KL_FWLOG_VALID_MASK 0x1ffff
int ath6kl_printk(const char *level, const char *fmt, ...)
{
@@ -280,6 +282,51 @@ static const struct file_operations fops_fwlog = {
.llseek = default_llseek,
};
+static ssize_t ath6kl_fwlog_mask_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct ath6kl *ar = file->private_data;
+ char buf[16];
+ int len;
+
+ len = snprintf(buf, sizeof(buf), "0x%x\n", ar->debug.fwlog_mask);
+
+ return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t ath6kl_fwlog_mask_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct ath6kl *ar = file->private_data;
+ char buf[16];
+ int ret, len;
+
+ len = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf,
+ count);
+ buf[len] = '\0';
+
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+
+ ret = kstrtou32(buf, 16, &ar->debug.fwlog_mask);
+ if (ret)
+ return ret;
+
+ ret = ath6kl_wmi_config_debug_module_cmd(ar->wmi,
+ ATH6KL_FWLOG_VALID_MASK,
+ ar->debug.fwlog_mask);
+ return ret;
+}
+
+static const struct file_operations fops_fwlog_mask = {
+ .open = ath6kl_debugfs_open,
+ .read = ath6kl_fwlog_mask_read,
+ .write = ath6kl_fwlog_mask_write,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+
static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
@@ -497,6 +544,12 @@ int ath6kl_debug_init(struct ath6kl *ar)
spin_lock_init(&ar->debug.fwlog_lock);
+ /*
+ * Actually we are lying here but don't know how to read the mask
+ * value from the firmware.
+ */
+ ar->debug.fwlog_mask = 0;
+
ar->debugfs_phy = debugfs_create_dir("ath6kl",
ar->wdev->wiphy->debugfsdir);
if (!ar->debugfs_phy) {
@@ -514,6 +567,9 @@ int ath6kl_debug_init(struct ath6kl *ar)
debugfs_create_file("fwlog", S_IRUSR, ar->debugfs_phy, ar,
&fops_fwlog);
+ debugfs_create_file("fwlog_mask", S_IRUSR | S_IWUSR, ar->debugfs_phy,
+ ar, &fops_fwlog_mask);
+
return 0;
}
diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c
index b96a734..fc40009 100644
--- a/drivers/net/wireless/ath/ath6kl/wmi.c
+++ b/drivers/net/wireless/ath/ath6kl/wmi.c
@@ -2203,6 +2203,25 @@ int ath6kl_wmi_get_challenge_resp_cmd(struct wmi *wmi, u32 cookie, u32 source)
return ret;
}
+int ath6kl_wmi_config_debug_module_cmd(struct wmi *wmi, u32 valid, u32 config)
+{
+ struct ath6kl_wmix_dbglog_cfg_module_cmd *cmd;
+ struct sk_buff *skb;
+ int ret;
+
+ skb = ath6kl_wmi_get_new_buf(sizeof(*cmd));
+ if (!skb)
+ return -ENOMEM;
+
+ cmd = (struct ath6kl_wmix_dbglog_cfg_module_cmd *) skb->data;
+ cmd->valid = cpu_to_le32(valid);
+ cmd->config = cpu_to_le32(config);
+
+ ret = ath6kl_wmi_cmd_send_xtnd(wmi, skb, WMIX_DBGLOG_CFG_MODULE_CMDID,
+ NO_SYNC_WMIFLAG);
+ return ret;
+}
+
int ath6kl_wmi_get_stats_cmd(struct wmi *wmi)
{
return ath6kl_wmi_simple_cmd(wmi, WMI_GET_STATISTICS_CMDID);
diff --git a/drivers/net/wireless/ath/ath6kl/wmi.h b/drivers/net/wireless/ath/ath6kl/wmi.h
index 8fa5d6e..240e64c 100644
--- a/drivers/net/wireless/ath/ath6kl/wmi.h
+++ b/drivers/net/wireless/ath/ath6kl/wmi.h
@@ -1898,6 +1898,11 @@ struct wmix_hb_challenge_resp_cmd {
__le32 source;
} __packed;
+struct ath6kl_wmix_dbglog_cfg_module_cmd {
+ __le32 valid;
+ __le32 config;
+} __packed;
+
/* End of Extended WMI (WMIX) */
enum wmi_sync_flag {
@@ -1977,6 +1982,7 @@ int ath6kl_wmi_set_lpreamble_cmd(struct wmi *wmi, u8 status,
u8 preamble_policy);
int ath6kl_wmi_get_challenge_resp_cmd(struct wmi *wmi, u32 cookie, u32 source);
+int ath6kl_wmi_config_debug_module_cmd(struct wmi *wmi, u32 valid, u32 config);
int ath6kl_wmi_get_stats_cmd(struct wmi *wmi);
int ath6kl_wmi_addkey_cmd(struct wmi *wmi, u8 key_index,
On Tue, Aug 30, 2011 at 04:44:58PM +0300, Kalle Valo wrote:
> Firmare sends the logs only when it's internal ring buffer is full. But
> if firmware crashes we need to retrieve the latest logs through diagnose
> window. This is now done everytime the debugfs file is read.
>
> Signed-off-by: Kalle Valo <[email protected]>
> ---
> drivers/net/wireless/ath/ath6kl/core.h | 15 ++++++
> drivers/net/wireless/ath/ath6kl/debug.c | 3 +
> drivers/net/wireless/ath/ath6kl/init.c | 13 -----
> drivers/net/wireless/ath/ath6kl/main.c | 75 ++++++++++++++++++++++++++++++
> drivers/net/wireless/ath/ath6kl/target.h | 14 ++++++
> 5 files changed, 107 insertions(+), 13 deletions(-)
>
> +int ath6kl_read_fwlogs(struct ath6kl *ar)
> +{
> + struct ath6kl_dbglog_hdr debug_hdr;
> + struct ath6kl_dbglog_buf debug_buf;
> + u32 address, length, dropped, firstbuf, debug_hdr_addr;
> + int ret = 0, loop;
> + u8 *buf;
> +
> + buf = kmalloc(ATH6KL_FWLOG_PAYLOAD_SIZE, GFP_KERNEL);
> + if (!buf)
> + return -ENOMEM;
> +
> + address = TARG_VTOP(ar->target_type,
> + ath6kl_get_hi_item_addr(ar,
> + HI_ITEM(hi_dbglog_hdr)));
> +
> + ret = ath6kl_diag_read32(ar, address, &debug_hdr_addr);
> + if (ret)
> + goto out;
> +
> + /* Get the contents of the ring buffer */
> + if (debug_hdr_addr == 0) {
> + ath6kl_warn("Invalid address for debug_hdr_addr\n");
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + address = TARG_VTOP(ar->target_type, debug_hdr_addr);
> + ath6kl_diag_read(ar, address, &debug_hdr, sizeof(debug_hdr));
> +
> + address = TARG_VTOP(ar->target_type,
> + le32_to_cpu(debug_hdr.dbuf_addr));
> + firstbuf = address;
> + dropped = le32_to_cpu(debug_hdr.dropped);
> + ath6kl_diag_read(ar, address, &debug_buf, sizeof(debug_buf));
> +
> + loop = 100;
> +
> + do {
> + address = TARG_VTOP(ar->target_type,
> + le32_to_cpu(debug_buf.buffer_addr));
> + length = le32_to_cpu(debug_buf.length);
> +
> + if (length != 0 && (le32_to_cpu(debug_buf.length) <=
> + le32_to_cpu(debug_buf.bufsize))) {
> + length = ALIGN(length, 4);
> +
> + ret = ath6kl_diag_read(ar, address,
> + buf, length);
> + if (ret)
> + goto out;
> +
> + ath6kl_debug_fwlog_event(ar, buf, length);
> + }
> +
> + address = TARG_VTOP(ar->target_type,
> + le32_to_cpu(debug_buf.next));
> + ath6kl_diag_read(ar, address, &debug_buf, sizeof(debug_buf));
Address passed to ath6kl_diag_read32() and ath6kl_diag_read() does
not seem to be endian safe. This is not just present in your patch,
it has been already there other places as well. I'll fix this up.
Vasanth