debug.c (8817B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Debugfs interface. 4 * 5 * Copyright (c) 2017-2020, Silicon Laboratories, Inc. 6 * Copyright (c) 2010, ST-Ericsson 7 */ 8#include <linux/debugfs.h> 9#include <linux/seq_file.h> 10#include <linux/crc32.h> 11 12#include "debug.h" 13#include "wfx.h" 14#include "sta.h" 15#include "main.h" 16#include "hif_tx.h" 17#include "hif_tx_mib.h" 18 19#define CREATE_TRACE_POINTS 20#include "traces.h" 21 22static const struct trace_print_flags hif_msg_print_map[] = { 23 hif_msg_list, 24}; 25 26static const struct trace_print_flags hif_mib_print_map[] = { 27 hif_mib_list, 28}; 29 30static const struct trace_print_flags wfx_reg_print_map[] = { 31 wfx_reg_list, 32}; 33 34static const char *get_symbol(unsigned long val, const struct trace_print_flags *symbol_array) 35{ 36 int i; 37 38 for (i = 0; symbol_array[i].mask != -1; i++) { 39 if (val == symbol_array[i].mask) 40 return symbol_array[i].name; 41 } 42 43 return "unknown"; 44} 45 46const char *wfx_get_hif_name(unsigned long id) 47{ 48 return get_symbol(id, hif_msg_print_map); 49} 50 51const char *wfx_get_mib_name(unsigned long id) 52{ 53 return get_symbol(id, hif_mib_print_map); 54} 55 56const char *wfx_get_reg_name(unsigned long id) 57{ 58 return get_symbol(id, wfx_reg_print_map); 59} 60 61static int wfx_counters_show(struct seq_file *seq, void *v) 62{ 63 int ret, i; 64 struct wfx_dev *wdev = seq->private; 65 struct wfx_hif_mib_extended_count_table counters[3]; 66 67 for (i = 0; i < ARRAY_SIZE(counters); i++) { 68 ret = wfx_hif_get_counters_table(wdev, i, counters + i); 69 if (ret < 0) 70 return ret; 71 if (ret > 0) 72 return -EIO; 73 } 74 75 seq_printf(seq, "%-24s %12s %12s %12s\n", "", "global", "iface 0", "iface 1"); 76 77#define PUT_COUNTER(name) \ 78 seq_printf(seq, "%-24s %12d %12d %12d\n", #name, \ 79 le32_to_cpu(counters[2].count_##name), \ 80 le32_to_cpu(counters[0].count_##name), \ 81 le32_to_cpu(counters[1].count_##name)) 82 83 PUT_COUNTER(tx_frames); 84 PUT_COUNTER(tx_frames_multicast); 85 PUT_COUNTER(tx_frames_success); 86 PUT_COUNTER(tx_frames_retried); 87 PUT_COUNTER(tx_frames_multi_retried); 88 PUT_COUNTER(tx_frames_failed); 89 90 PUT_COUNTER(ack_failed); 91 PUT_COUNTER(rts_success); 92 PUT_COUNTER(rts_failed); 93 94 PUT_COUNTER(rx_frames); 95 PUT_COUNTER(rx_frames_multicast); 96 PUT_COUNTER(rx_frames_success); 97 PUT_COUNTER(rx_frames_failed); 98 PUT_COUNTER(drop_plcp); 99 PUT_COUNTER(drop_fcs); 100 PUT_COUNTER(drop_no_key); 101 PUT_COUNTER(drop_decryption); 102 PUT_COUNTER(drop_tkip_mic); 103 PUT_COUNTER(drop_bip_mic); 104 PUT_COUNTER(drop_cmac_icv); 105 PUT_COUNTER(drop_cmac_replay); 106 PUT_COUNTER(drop_ccmp_replay); 107 PUT_COUNTER(drop_duplicate); 108 109 PUT_COUNTER(rx_bcn_miss); 110 PUT_COUNTER(rx_bcn_success); 111 PUT_COUNTER(rx_bcn_dtim); 112 PUT_COUNTER(rx_bcn_dtim_aid0_clr); 113 PUT_COUNTER(rx_bcn_dtim_aid0_set); 114 115#undef PUT_COUNTER 116 117 for (i = 0; i < ARRAY_SIZE(counters[0].reserved); i++) 118 seq_printf(seq, "reserved[%02d]%12s %12d %12d %12d\n", i, "", 119 le32_to_cpu(counters[2].reserved[i]), 120 le32_to_cpu(counters[0].reserved[i]), 121 le32_to_cpu(counters[1].reserved[i])); 122 123 return 0; 124} 125DEFINE_SHOW_ATTRIBUTE(wfx_counters); 126 127static const char * const channel_names[] = { 128 [0] = "1M", 129 [1] = "2M", 130 [2] = "5.5M", 131 [3] = "11M", 132 /* Entries 4 and 5 does not exist */ 133 [6] = "6M", 134 [7] = "9M", 135 [8] = "12M", 136 [9] = "18M", 137 [10] = "24M", 138 [11] = "36M", 139 [12] = "48M", 140 [13] = "54M", 141 [14] = "MCS0", 142 [15] = "MCS1", 143 [16] = "MCS2", 144 [17] = "MCS3", 145 [18] = "MCS4", 146 [19] = "MCS5", 147 [20] = "MCS6", 148 [21] = "MCS7", 149}; 150 151static int wfx_rx_stats_show(struct seq_file *seq, void *v) 152{ 153 struct wfx_dev *wdev = seq->private; 154 struct wfx_hif_rx_stats *st = &wdev->rx_stats; 155 int i; 156 157 mutex_lock(&wdev->rx_stats_lock); 158 seq_printf(seq, "Timestamp: %dus\n", st->date); 159 seq_printf(seq, "Low power clock: frequency %uHz, external %s\n", 160 le32_to_cpu(st->pwr_clk_freq), st->is_ext_pwr_clk ? "yes" : "no"); 161 seq_printf(seq, "Num. of frames: %d, PER (x10e4): %d, Throughput: %dKbps/s\n", 162 st->nb_rx_frame, st->per_total, st->throughput); 163 seq_puts(seq, " Num. of PER RSSI SNR CFO\n"); 164 seq_puts(seq, " frames (x10e4) (dBm) (dB) (kHz)\n"); 165 for (i = 0; i < ARRAY_SIZE(channel_names); i++) { 166 if (channel_names[i]) 167 seq_printf(seq, "%5s %8d %8d %8d %8d %8d\n", 168 channel_names[i], 169 le32_to_cpu(st->nb_rx_by_rate[i]), 170 le16_to_cpu(st->per[i]), 171 (s16)le16_to_cpu(st->rssi[i]) / 100, 172 (s16)le16_to_cpu(st->snr[i]) / 100, 173 (s16)le16_to_cpu(st->cfo[i])); 174 } 175 mutex_unlock(&wdev->rx_stats_lock); 176 177 return 0; 178} 179DEFINE_SHOW_ATTRIBUTE(wfx_rx_stats); 180 181static int wfx_tx_power_loop_show(struct seq_file *seq, void *v) 182{ 183 struct wfx_dev *wdev = seq->private; 184 struct wfx_hif_tx_power_loop_info *st = &wdev->tx_power_loop_info; 185 int tmp; 186 187 mutex_lock(&wdev->tx_power_loop_info_lock); 188 tmp = le16_to_cpu(st->tx_gain_dig); 189 seq_printf(seq, "Tx gain digital: %d\n", tmp); 190 tmp = le16_to_cpu(st->tx_gain_pa); 191 seq_printf(seq, "Tx gain PA: %d\n", tmp); 192 tmp = (s16)le16_to_cpu(st->target_pout); 193 seq_printf(seq, "Target Pout: %d.%02d dBm\n", tmp / 4, (tmp % 4) * 25); 194 tmp = (s16)le16_to_cpu(st->p_estimation); 195 seq_printf(seq, "FEM Pout: %d.%02d dBm\n", tmp / 4, (tmp % 4) * 25); 196 tmp = le16_to_cpu(st->vpdet); 197 seq_printf(seq, "Vpdet: %d mV\n", tmp); 198 seq_printf(seq, "Measure index: %d\n", st->measurement_index); 199 mutex_unlock(&wdev->tx_power_loop_info_lock); 200 201 return 0; 202} 203DEFINE_SHOW_ATTRIBUTE(wfx_tx_power_loop); 204 205static ssize_t wfx_send_pds_write(struct file *file, const char __user *user_buf, 206 size_t count, loff_t *ppos) 207{ 208 struct wfx_dev *wdev = file->private_data; 209 char *buf; 210 int ret; 211 212 if (*ppos != 0) { 213 dev_dbg(wdev->dev, "PDS data must be written in one transaction"); 214 return -EBUSY; 215 } 216 buf = memdup_user(user_buf, count); 217 if (IS_ERR(buf)) 218 return PTR_ERR(buf); 219 *ppos = *ppos + count; 220 ret = wfx_send_pds(wdev, buf, count); 221 kfree(buf); 222 if (ret < 0) 223 return ret; 224 return count; 225} 226 227static const struct file_operations wfx_send_pds_fops = { 228 .open = simple_open, 229 .write = wfx_send_pds_write, 230}; 231 232struct dbgfs_hif_msg { 233 struct wfx_dev *wdev; 234 struct completion complete; 235 u8 reply[1024]; 236 int ret; 237}; 238 239static ssize_t wfx_send_hif_msg_write(struct file *file, const char __user *user_buf, 240 size_t count, loff_t *ppos) 241{ 242 struct dbgfs_hif_msg *context = file->private_data; 243 struct wfx_dev *wdev = context->wdev; 244 struct wfx_hif_msg *request; 245 246 if (completion_done(&context->complete)) { 247 dev_dbg(wdev->dev, "read previous result before start a new one\n"); 248 return -EBUSY; 249 } 250 if (count < sizeof(struct wfx_hif_msg)) 251 return -EINVAL; 252 253 /* wfx_cmd_send() checks that reply buffer is wide enough, but does not return precise 254 * length read. User have to know how many bytes should be read. Filling reply buffer with a 255 * memory pattern may help user. 256 */ 257 memset(context->reply, 0xFF, sizeof(context->reply)); 258 request = memdup_user(user_buf, count); 259 if (IS_ERR(request)) 260 return PTR_ERR(request); 261 if (le16_to_cpu(request->len) != count) { 262 kfree(request); 263 return -EINVAL; 264 } 265 context->ret = wfx_cmd_send(wdev, request, context->reply, sizeof(context->reply), false); 266 267 kfree(request); 268 complete(&context->complete); 269 return count; 270} 271 272static ssize_t wfx_send_hif_msg_read(struct file *file, char __user *user_buf, 273 size_t count, loff_t *ppos) 274{ 275 struct dbgfs_hif_msg *context = file->private_data; 276 int ret; 277 278 if (count > sizeof(context->reply)) 279 return -EINVAL; 280 ret = wait_for_completion_interruptible(&context->complete); 281 if (ret) 282 return ret; 283 if (context->ret < 0) 284 return context->ret; 285 /* Be careful, write() is waiting for a full message while read() only returns a payload */ 286 if (copy_to_user(user_buf, context->reply, count)) 287 return -EFAULT; 288 289 return count; 290} 291 292static int wfx_send_hif_msg_open(struct inode *inode, struct file *file) 293{ 294 struct dbgfs_hif_msg *context = kzalloc(sizeof(*context), GFP_KERNEL); 295 296 if (!context) 297 return -ENOMEM; 298 context->wdev = inode->i_private; 299 init_completion(&context->complete); 300 file->private_data = context; 301 return 0; 302} 303 304static int wfx_send_hif_msg_release(struct inode *inode, struct file *file) 305{ 306 struct dbgfs_hif_msg *context = file->private_data; 307 308 kfree(context); 309 return 0; 310} 311 312static const struct file_operations wfx_send_hif_msg_fops = { 313 .open = wfx_send_hif_msg_open, 314 .release = wfx_send_hif_msg_release, 315 .write = wfx_send_hif_msg_write, 316 .read = wfx_send_hif_msg_read, 317}; 318 319int wfx_debug_init(struct wfx_dev *wdev) 320{ 321 struct dentry *d; 322 323 d = debugfs_create_dir("wfx", wdev->hw->wiphy->debugfsdir); 324 debugfs_create_file("counters", 0444, d, wdev, &wfx_counters_fops); 325 debugfs_create_file("rx_stats", 0444, d, wdev, &wfx_rx_stats_fops); 326 debugfs_create_file("tx_power_loop", 0444, d, wdev, &wfx_tx_power_loop_fops); 327 debugfs_create_file("send_pds", 0200, d, wdev, &wfx_send_pds_fops); 328 debugfs_create_file("send_hif_msg", 0600, d, wdev, &wfx_send_hif_msg_fops); 329 330 return 0; 331}