diff options
author | Kalle Valo <kvalo@qca.qualcomm.com> | 2011-09-02 03:32:04 -0400 |
---|---|---|
committer | Kalle Valo <kvalo@qca.qualcomm.com> | 2011-09-02 03:32:04 -0400 |
commit | bdf5396be177b689c00ae6ebed00d13fafaed36e (patch) | |
tree | b185c185ab024aca98871ce06d4a3fbc37c4b5ab /drivers/net/wireless/ath/ath6kl/debug.c | |
parent | d748753cd71f4504129fc6bd2262e0c5e4abe62f (diff) |
ath6kl: add firmware log support
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 <kvalo@qca.qualcomm.com>
Diffstat (limited to 'drivers/net/wireless/ath/ath6kl/debug.c')
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/debug.c | 154 |
1 files changed, 153 insertions, 1 deletions
diff --git a/drivers/net/wireless/ath/ath6kl/debug.c b/drivers/net/wireless/ath/ath6kl/debug.c index 2b462876cec1..b2706da58149 100644 --- a/drivers/net/wireless/ath/ath6kl/debug.c +++ b/drivers/net/wireless/ath/ath6kl/debug.c | |||
@@ -15,7 +15,23 @@ | |||
15 | */ | 15 | */ |
16 | 16 | ||
17 | #include "core.h" | 17 | #include "core.h" |
18 | |||
19 | #include <linux/circ_buf.h> | ||
20 | |||
18 | #include "debug.h" | 21 | #include "debug.h" |
22 | #include "target.h" | ||
23 | |||
24 | struct ath6kl_fwlog_slot { | ||
25 | __le32 timestamp; | ||
26 | __le32 length; | ||
27 | |||
28 | /* max ATH6KL_FWLOG_PAYLOAD_SIZE bytes */ | ||
29 | u8 payload[0]; | ||
30 | }; | ||
31 | |||
32 | #define ATH6KL_FWLOG_SIZE 32768 | ||
33 | #define ATH6KL_FWLOG_SLOT_SIZE (sizeof(struct ath6kl_fwlog_slot) + \ | ||
34 | ATH6KL_FWLOG_PAYLOAD_SIZE) | ||
19 | 35 | ||
20 | int ath6kl_printk(const char *level, const char *fmt, ...) | 36 | int ath6kl_printk(const char *level, const char *fmt, ...) |
21 | { | 37 | { |
@@ -153,6 +169,117 @@ static int ath6kl_debugfs_open(struct inode *inode, struct file *file) | |||
153 | return 0; | 169 | return 0; |
154 | } | 170 | } |
155 | 171 | ||
172 | static void ath6kl_debug_fwlog_add(struct ath6kl *ar, const void *buf, | ||
173 | size_t buf_len) | ||
174 | { | ||
175 | struct circ_buf *fwlog = &ar->debug.fwlog_buf; | ||
176 | size_t space; | ||
177 | int i; | ||
178 | |||
179 | /* entries must all be equal size */ | ||
180 | if (WARN_ON(buf_len != ATH6KL_FWLOG_SLOT_SIZE)) | ||
181 | return; | ||
182 | |||
183 | space = CIRC_SPACE(fwlog->head, fwlog->tail, ATH6KL_FWLOG_SIZE); | ||
184 | if (space < buf_len) | ||
185 | /* discard oldest slot */ | ||
186 | fwlog->tail = (fwlog->tail + ATH6KL_FWLOG_SLOT_SIZE) & | ||
187 | (ATH6KL_FWLOG_SIZE - 1); | ||
188 | |||
189 | for (i = 0; i < buf_len; i += space) { | ||
190 | space = CIRC_SPACE_TO_END(fwlog->head, fwlog->tail, | ||
191 | ATH6KL_FWLOG_SIZE); | ||
192 | |||
193 | if ((size_t) space > buf_len - i) | ||
194 | space = buf_len - i; | ||
195 | |||
196 | memcpy(&fwlog->buf[fwlog->head], buf, space); | ||
197 | fwlog->head = (fwlog->head + space) & (ATH6KL_FWLOG_SIZE - 1); | ||
198 | } | ||
199 | |||
200 | } | ||
201 | |||
202 | void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len) | ||
203 | { | ||
204 | struct ath6kl_fwlog_slot *slot = ar->debug.fwlog_tmp; | ||
205 | size_t slot_len; | ||
206 | |||
207 | if (WARN_ON(len > ATH6KL_FWLOG_PAYLOAD_SIZE)) | ||
208 | return; | ||
209 | |||
210 | spin_lock_bh(&ar->debug.fwlog_lock); | ||
211 | |||
212 | slot->timestamp = cpu_to_le32(jiffies); | ||
213 | slot->length = cpu_to_le32(len); | ||
214 | memcpy(slot->payload, buf, len); | ||
215 | |||
216 | slot_len = sizeof(*slot) + len; | ||
217 | |||
218 | if (slot_len < ATH6KL_FWLOG_SLOT_SIZE) | ||
219 | memset(slot->payload + len, 0, | ||
220 | ATH6KL_FWLOG_SLOT_SIZE - slot_len); | ||
221 | |||
222 | ath6kl_debug_fwlog_add(ar, slot, ATH6KL_FWLOG_SLOT_SIZE); | ||
223 | |||
224 | spin_unlock_bh(&ar->debug.fwlog_lock); | ||
225 | } | ||
226 | |||
227 | static bool ath6kl_debug_fwlog_empty(struct ath6kl *ar) | ||
228 | { | ||
229 | return CIRC_CNT(ar->debug.fwlog_buf.head, | ||
230 | ar->debug.fwlog_buf.tail, | ||
231 | ATH6KL_FWLOG_SLOT_SIZE) == 0; | ||
232 | } | ||
233 | |||
234 | static ssize_t ath6kl_fwlog_read(struct file *file, char __user *user_buf, | ||
235 | size_t count, loff_t *ppos) | ||
236 | { | ||
237 | struct ath6kl *ar = file->private_data; | ||
238 | struct circ_buf *fwlog = &ar->debug.fwlog_buf; | ||
239 | size_t len = 0, buf_len = count; | ||
240 | ssize_t ret_cnt; | ||
241 | char *buf; | ||
242 | int ccnt; | ||
243 | |||
244 | buf = vmalloc(buf_len); | ||
245 | if (!buf) | ||
246 | return -ENOMEM; | ||
247 | |||
248 | spin_lock_bh(&ar->debug.fwlog_lock); | ||
249 | |||
250 | while (len < buf_len && !ath6kl_debug_fwlog_empty(ar)) { | ||
251 | ccnt = CIRC_CNT_TO_END(fwlog->head, fwlog->tail, | ||
252 | ATH6KL_FWLOG_SIZE); | ||
253 | |||
254 | if ((size_t) ccnt > buf_len - len) | ||
255 | ccnt = buf_len - len; | ||
256 | |||
257 | memcpy(buf + len, &fwlog->buf[fwlog->tail], ccnt); | ||
258 | len += ccnt; | ||
259 | |||
260 | fwlog->tail = (fwlog->tail + ccnt) & | ||
261 | (ATH6KL_FWLOG_SIZE - 1); | ||
262 | } | ||
263 | |||
264 | spin_unlock_bh(&ar->debug.fwlog_lock); | ||
265 | |||
266 | if (WARN_ON(len > buf_len)) | ||
267 | len = buf_len; | ||
268 | |||
269 | ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
270 | |||
271 | vfree(buf); | ||
272 | |||
273 | return ret_cnt; | ||
274 | } | ||
275 | |||
276 | static const struct file_operations fops_fwlog = { | ||
277 | .open = ath6kl_debugfs_open, | ||
278 | .read = ath6kl_fwlog_read, | ||
279 | .owner = THIS_MODULE, | ||
280 | .llseek = default_llseek, | ||
281 | }; | ||
282 | |||
156 | static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf, | 283 | static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf, |
157 | size_t count, loff_t *ppos) | 284 | size_t count, loff_t *ppos) |
158 | { | 285 | { |
@@ -358,10 +485,25 @@ static const struct file_operations fops_credit_dist_stats = { | |||
358 | 485 | ||
359 | int ath6kl_debug_init(struct ath6kl *ar) | 486 | int ath6kl_debug_init(struct ath6kl *ar) |
360 | { | 487 | { |
488 | ar->debug.fwlog_buf.buf = vmalloc(ATH6KL_FWLOG_SIZE); | ||
489 | if (ar->debug.fwlog_buf.buf == NULL) | ||
490 | return -ENOMEM; | ||
491 | |||
492 | ar->debug.fwlog_tmp = kmalloc(ATH6KL_FWLOG_SLOT_SIZE, GFP_KERNEL); | ||
493 | if (ar->debug.fwlog_tmp == NULL) { | ||
494 | vfree(ar->debug.fwlog_buf.buf); | ||
495 | return -ENOMEM; | ||
496 | } | ||
497 | |||
498 | spin_lock_init(&ar->debug.fwlog_lock); | ||
499 | |||
361 | ar->debugfs_phy = debugfs_create_dir("ath6kl", | 500 | ar->debugfs_phy = debugfs_create_dir("ath6kl", |
362 | ar->wdev->wiphy->debugfsdir); | 501 | ar->wdev->wiphy->debugfsdir); |
363 | if (!ar->debugfs_phy) | 502 | if (!ar->debugfs_phy) { |
503 | vfree(ar->debug.fwlog_buf.buf); | ||
504 | kfree(ar->debug.fwlog_tmp); | ||
364 | return -ENOMEM; | 505 | return -ENOMEM; |
506 | } | ||
365 | 507 | ||
366 | debugfs_create_file("tgt_stats", S_IRUSR, ar->debugfs_phy, ar, | 508 | debugfs_create_file("tgt_stats", S_IRUSR, ar->debugfs_phy, ar, |
367 | &fops_tgt_stats); | 509 | &fops_tgt_stats); |
@@ -369,6 +511,16 @@ int ath6kl_debug_init(struct ath6kl *ar) | |||
369 | debugfs_create_file("credit_dist_stats", S_IRUSR, ar->debugfs_phy, ar, | 511 | debugfs_create_file("credit_dist_stats", S_IRUSR, ar->debugfs_phy, ar, |
370 | &fops_credit_dist_stats); | 512 | &fops_credit_dist_stats); |
371 | 513 | ||
514 | debugfs_create_file("fwlog", S_IRUSR, ar->debugfs_phy, ar, | ||
515 | &fops_fwlog); | ||
516 | |||
372 | return 0; | 517 | return 0; |
373 | } | 518 | } |
519 | |||
520 | void ath6kl_debug_cleanup(struct ath6kl *ar) | ||
521 | { | ||
522 | vfree(ar->debug.fwlog_buf.buf); | ||
523 | kfree(ar->debug.fwlog_tmp); | ||
524 | } | ||
525 | |||
374 | #endif | 526 | #endif |