Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/contrib/dev/iwlwifi/fw/debugfs.c
48287 views
1
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2
/*
3
* Copyright (C) 2012-2014, 2018-2024 Intel Corporation
4
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
5
* Copyright (C) 2016-2017 Intel Deutschland GmbH
6
*/
7
#include "api/commands.h"
8
#include "debugfs.h"
9
#include "dbg.h"
10
#include <linux/seq_file.h>
11
12
#define FWRT_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \
13
struct dbgfs_##name##_data { \
14
argtype *arg; \
15
bool read_done; \
16
ssize_t rlen; \
17
char rbuf[buflen]; \
18
}; \
19
static int _iwl_dbgfs_##name##_open(struct inode *inode, \
20
struct file *file) \
21
{ \
22
struct dbgfs_##name##_data *data; \
23
\
24
data = kzalloc(sizeof(*data), GFP_KERNEL); \
25
if (!data) \
26
return -ENOMEM; \
27
\
28
data->read_done = false; \
29
data->arg = inode->i_private; \
30
file->private_data = data; \
31
\
32
return 0; \
33
}
34
35
#define FWRT_DEBUGFS_READ_WRAPPER(name) \
36
static ssize_t _iwl_dbgfs_##name##_read(struct file *file, \
37
char __user *user_buf, \
38
size_t count, loff_t *ppos) \
39
{ \
40
struct dbgfs_##name##_data *data = file->private_data; \
41
\
42
if (!data->read_done) { \
43
data->read_done = true; \
44
data->rlen = iwl_dbgfs_##name##_read(data->arg, \
45
sizeof(data->rbuf),\
46
data->rbuf); \
47
} \
48
\
49
if (data->rlen < 0) \
50
return data->rlen; \
51
return simple_read_from_buffer(user_buf, count, ppos, \
52
data->rbuf, data->rlen); \
53
}
54
55
static int _iwl_dbgfs_release(struct inode *inode, struct file *file)
56
{
57
kfree(file->private_data);
58
59
return 0;
60
}
61
62
#define _FWRT_DEBUGFS_READ_FILE_OPS(name, buflen, argtype) \
63
FWRT_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \
64
FWRT_DEBUGFS_READ_WRAPPER(name) \
65
static const struct file_operations iwl_dbgfs_##name##_ops = { \
66
.read = _iwl_dbgfs_##name##_read, \
67
.open = _iwl_dbgfs_##name##_open, \
68
.llseek = generic_file_llseek, \
69
.release = _iwl_dbgfs_release, \
70
}
71
72
#define FWRT_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \
73
static ssize_t _iwl_dbgfs_##name##_write(struct file *file, \
74
const char __user *user_buf, \
75
size_t count, loff_t *ppos) \
76
{ \
77
argtype *arg = \
78
((struct dbgfs_##name##_data *)file->private_data)->arg;\
79
char buf[buflen] = {}; \
80
size_t buf_size = min(count, sizeof(buf) - 1); \
81
\
82
if (copy_from_user(buf, user_buf, buf_size)) \
83
return -EFAULT; \
84
\
85
return iwl_dbgfs_##name##_write(arg, buf, buf_size); \
86
}
87
88
#define _FWRT_DEBUGFS_READ_WRITE_FILE_OPS(name, buflen, argtype) \
89
FWRT_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \
90
FWRT_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \
91
FWRT_DEBUGFS_READ_WRAPPER(name) \
92
static const struct file_operations iwl_dbgfs_##name##_ops = { \
93
.write = _iwl_dbgfs_##name##_write, \
94
.read = _iwl_dbgfs_##name##_read, \
95
.open = _iwl_dbgfs_##name##_open, \
96
.llseek = generic_file_llseek, \
97
.release = _iwl_dbgfs_release, \
98
}
99
100
#define _FWRT_DEBUGFS_WRITE_FILE_OPS(name, buflen, argtype) \
101
FWRT_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \
102
FWRT_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \
103
static const struct file_operations iwl_dbgfs_##name##_ops = { \
104
.write = _iwl_dbgfs_##name##_write, \
105
.open = _iwl_dbgfs_##name##_open, \
106
.llseek = generic_file_llseek, \
107
.release = _iwl_dbgfs_release, \
108
}
109
110
#define FWRT_DEBUGFS_READ_FILE_OPS(name, bufsz) \
111
_FWRT_DEBUGFS_READ_FILE_OPS(name, bufsz, struct iwl_fw_runtime)
112
113
#define FWRT_DEBUGFS_WRITE_FILE_OPS(name, bufsz) \
114
_FWRT_DEBUGFS_WRITE_FILE_OPS(name, bufsz, struct iwl_fw_runtime)
115
116
#define FWRT_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz) \
117
_FWRT_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz, struct iwl_fw_runtime)
118
119
#define FWRT_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) do { \
120
debugfs_create_file(alias, mode, parent, fwrt, \
121
&iwl_dbgfs_##name##_ops); \
122
} while (0)
123
#define FWRT_DEBUGFS_ADD_FILE(name, parent, mode) \
124
FWRT_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
125
126
static ssize_t iwl_dbgfs_fw_dbg_collect_write(struct iwl_fw_runtime *fwrt,
127
char *buf, size_t count)
128
{
129
if (count == 0)
130
return 0;
131
132
if (!iwl_trans_fw_running(fwrt->trans))
133
return count;
134
135
iwl_dbg_tlv_time_point(fwrt, IWL_FW_INI_TIME_POINT_USER_TRIGGER, NULL);
136
137
iwl_fw_dbg_collect(fwrt, FW_DBG_TRIGGER_USER, buf, (count - 1), NULL);
138
139
return count;
140
}
141
142
FWRT_DEBUGFS_WRITE_FILE_OPS(fw_dbg_collect, 16);
143
144
static int iwl_dbgfs_enabled_severities_write(struct iwl_fw_runtime *fwrt,
145
char *buf, size_t count)
146
{
147
struct iwl_dbg_host_event_cfg_cmd event_cfg;
148
struct iwl_host_cmd hcmd = {
149
.id = WIDE_ID(DEBUG_GROUP, HOST_EVENT_CFG),
150
.flags = CMD_ASYNC,
151
.data[0] = &event_cfg,
152
.len[0] = sizeof(event_cfg),
153
};
154
u32 enabled_severities;
155
int ret = kstrtou32(buf, 10, &enabled_severities);
156
157
if (ret < 0)
158
return ret;
159
160
event_cfg.enabled_severities = cpu_to_le32(enabled_severities);
161
162
if (fwrt->ops && fwrt->ops->send_hcmd)
163
ret = fwrt->ops->send_hcmd(fwrt->ops_ctx, &hcmd);
164
else
165
ret = -EPERM;
166
167
IWL_INFO(fwrt,
168
"sent host event cfg with enabled_severities: %u, ret: %d\n",
169
enabled_severities, ret);
170
171
return ret ?: count;
172
}
173
174
FWRT_DEBUGFS_WRITE_FILE_OPS(enabled_severities, 16);
175
176
static void iwl_fw_timestamp_marker_wk(struct work_struct *work)
177
{
178
int ret;
179
struct iwl_fw_runtime *fwrt =
180
container_of(work, struct iwl_fw_runtime, timestamp.wk.work);
181
unsigned long delay = fwrt->timestamp.delay;
182
183
ret = iwl_fw_send_timestamp_marker_cmd(fwrt);
184
if (!ret && delay)
185
schedule_delayed_work(&fwrt->timestamp.wk,
186
round_jiffies_relative(delay));
187
else
188
IWL_INFO(fwrt,
189
"stopping timestamp_marker, ret: %d, delay: %u\n",
190
ret, jiffies_to_msecs(delay) / 1000);
191
}
192
193
void iwl_fw_trigger_timestamp(struct iwl_fw_runtime *fwrt, u32 delay)
194
{
195
IWL_INFO(fwrt,
196
"starting timestamp_marker trigger with delay: %us\n",
197
delay);
198
199
iwl_fw_cancel_timestamp(fwrt);
200
201
fwrt->timestamp.delay = secs_to_jiffies(delay);
202
203
schedule_delayed_work(&fwrt->timestamp.wk,
204
round_jiffies_relative(fwrt->timestamp.delay));
205
}
206
207
static ssize_t iwl_dbgfs_timestamp_marker_write(struct iwl_fw_runtime *fwrt,
208
char *buf, size_t count)
209
{
210
int ret;
211
u32 delay;
212
213
ret = kstrtou32(buf, 10, &delay);
214
if (ret < 0)
215
return ret;
216
217
iwl_fw_trigger_timestamp(fwrt, delay);
218
219
return count;
220
}
221
222
static ssize_t iwl_dbgfs_timestamp_marker_read(struct iwl_fw_runtime *fwrt,
223
size_t size, char *buf)
224
{
225
u32 delay_secs = jiffies_to_msecs(fwrt->timestamp.delay) / 1000;
226
227
return scnprintf(buf, size, "%d\n", delay_secs);
228
}
229
230
FWRT_DEBUGFS_READ_WRITE_FILE_OPS(timestamp_marker, 16);
231
232
struct hcmd_write_data {
233
__be32 cmd_id;
234
__be32 flags;
235
__be16 length;
236
u8 data[];
237
} __packed;
238
239
static ssize_t iwl_dbgfs_send_hcmd_write(struct iwl_fw_runtime *fwrt, char *buf,
240
size_t count)
241
{
242
size_t header_size = (sizeof(u32) * 2 + sizeof(u16)) * 2;
243
size_t data_size = (count - 1) / 2;
244
int ret;
245
struct hcmd_write_data *data;
246
struct iwl_host_cmd hcmd = {
247
.len = { 0, },
248
.data = { NULL, },
249
};
250
251
if (!iwl_trans_fw_running(fwrt->trans))
252
return -EIO;
253
254
if (count < header_size + 1 || count > 1024 * 4)
255
return -EINVAL;
256
257
data = kmalloc(data_size, GFP_KERNEL);
258
if (!data)
259
return -ENOMEM;
260
261
ret = hex2bin((u8 *)data, buf, data_size);
262
if (ret)
263
goto out;
264
265
hcmd.id = be32_to_cpu(data->cmd_id);
266
hcmd.flags = be32_to_cpu(data->flags);
267
hcmd.len[0] = be16_to_cpu(data->length);
268
hcmd.data[0] = data->data;
269
270
if (count != header_size + hcmd.len[0] * 2 + 1) {
271
IWL_ERR(fwrt,
272
"host command data size does not match header length\n");
273
ret = -EINVAL;
274
goto out;
275
}
276
277
if (fwrt->ops && fwrt->ops->send_hcmd)
278
ret = fwrt->ops->send_hcmd(fwrt->ops_ctx, &hcmd);
279
else
280
ret = -EPERM;
281
282
if (ret < 0)
283
goto out;
284
285
if (hcmd.flags & CMD_WANT_SKB)
286
iwl_free_resp(&hcmd);
287
out:
288
kfree(data);
289
return ret ?: count;
290
}
291
292
FWRT_DEBUGFS_WRITE_FILE_OPS(send_hcmd, 512);
293
294
static ssize_t iwl_dbgfs_fw_dbg_domain_read(struct iwl_fw_runtime *fwrt,
295
size_t size, char *buf)
296
{
297
return scnprintf(buf, size, "0x%08x\n",
298
fwrt->trans->dbg.domains_bitmap);
299
}
300
301
FWRT_DEBUGFS_READ_FILE_OPS(fw_dbg_domain, 20);
302
303
static ssize_t iwl_dbgfs_fw_ver_read(struct iwl_fw_runtime *fwrt,
304
size_t size, char *buf)
305
{
306
char *pos = buf;
307
char *endpos = buf + size;
308
309
pos += scnprintf(pos, endpos - pos, "FW id: %s\n",
310
fwrt->fw->fw_version);
311
pos += scnprintf(pos, endpos - pos, "FW: %s\n",
312
fwrt->fw->human_readable);
313
pos += scnprintf(pos, endpos - pos, "Device: %s\n",
314
fwrt->trans->info.name);
315
pos += scnprintf(pos, endpos - pos, "Bus: %s\n",
316
fwrt->dev->bus->name);
317
318
return pos - buf;
319
}
320
321
FWRT_DEBUGFS_READ_FILE_OPS(fw_ver, 1024);
322
323
struct iwl_dbgfs_fw_info_priv {
324
struct iwl_fw_runtime *fwrt;
325
};
326
327
struct iwl_dbgfs_fw_info_state {
328
loff_t pos;
329
};
330
331
static void *iwl_dbgfs_fw_info_seq_next(struct seq_file *seq,
332
void *v, loff_t *pos)
333
{
334
struct iwl_dbgfs_fw_info_state *state = v;
335
struct iwl_dbgfs_fw_info_priv *priv = seq->private;
336
const struct iwl_fw *fw = priv->fwrt->fw;
337
338
*pos = ++state->pos;
339
if (*pos >= fw->ucode_capa.n_cmd_versions) {
340
kfree(state);
341
return NULL;
342
}
343
344
return state;
345
}
346
347
static void iwl_dbgfs_fw_info_seq_stop(struct seq_file *seq,
348
void *v)
349
{
350
kfree(v);
351
}
352
353
static void *iwl_dbgfs_fw_info_seq_start(struct seq_file *seq, loff_t *pos)
354
{
355
struct iwl_dbgfs_fw_info_priv *priv = seq->private;
356
const struct iwl_fw *fw = priv->fwrt->fw;
357
struct iwl_dbgfs_fw_info_state *state;
358
359
if (*pos >= fw->ucode_capa.n_cmd_versions)
360
return NULL;
361
362
state = kzalloc(sizeof(*state), GFP_KERNEL);
363
if (!state)
364
return NULL;
365
state->pos = *pos;
366
return state;
367
};
368
369
static int iwl_dbgfs_fw_info_seq_show(struct seq_file *seq, void *v)
370
{
371
struct iwl_dbgfs_fw_info_state *state = v;
372
struct iwl_dbgfs_fw_info_priv *priv = seq->private;
373
const struct iwl_fw *fw = priv->fwrt->fw;
374
const struct iwl_fw_cmd_version *ver;
375
u32 cmd_id;
376
int has_capa;
377
378
if (!state->pos) {
379
seq_puts(seq, "fw_capa:\n");
380
has_capa = fw_has_capa(&fw->ucode_capa,
381
IWL_UCODE_TLV_CAPA_PPAG_CHINA_BIOS_SUPPORT) ? 1 : 0;
382
seq_printf(seq,
383
" %d: %d\n",
384
IWL_UCODE_TLV_CAPA_PPAG_CHINA_BIOS_SUPPORT,
385
has_capa);
386
has_capa = fw_has_capa(&fw->ucode_capa,
387
IWL_UCODE_TLV_CAPA_CHINA_22_REG_SUPPORT) ? 1 : 0;
388
seq_printf(seq,
389
" %d: %d\n",
390
IWL_UCODE_TLV_CAPA_CHINA_22_REG_SUPPORT,
391
has_capa);
392
has_capa = fw_has_capa(&fw->ucode_capa,
393
IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE) ? 1 : 0;
394
seq_printf(seq,
395
" %d: %d\n",
396
IWL_UCODE_TLV_CAPA_FW_ACCEPTS_RAW_DSM_TABLE,
397
has_capa);
398
seq_puts(seq, "fw_api_ver:\n");
399
}
400
401
ver = &fw->ucode_capa.cmd_versions[state->pos];
402
403
cmd_id = WIDE_ID(ver->group, ver->cmd);
404
405
seq_printf(seq, " 0x%04x:\n", cmd_id);
406
seq_printf(seq, " name: %s\n",
407
iwl_get_cmd_string(priv->fwrt->trans, cmd_id));
408
seq_printf(seq, " cmd_ver: %d\n", ver->cmd_ver);
409
seq_printf(seq, " notif_ver: %d\n", ver->notif_ver);
410
return 0;
411
}
412
413
static const struct seq_operations iwl_dbgfs_info_seq_ops = {
414
.start = iwl_dbgfs_fw_info_seq_start,
415
.next = iwl_dbgfs_fw_info_seq_next,
416
.stop = iwl_dbgfs_fw_info_seq_stop,
417
.show = iwl_dbgfs_fw_info_seq_show,
418
};
419
420
static int iwl_dbgfs_fw_info_open(struct inode *inode, struct file *filp)
421
{
422
struct iwl_dbgfs_fw_info_priv *priv;
423
424
priv = __seq_open_private(filp, &iwl_dbgfs_info_seq_ops,
425
sizeof(*priv));
426
427
if (!priv)
428
return -ENOMEM;
429
430
priv->fwrt = inode->i_private;
431
return 0;
432
}
433
434
static const struct file_operations iwl_dbgfs_fw_info_ops = {
435
.owner = THIS_MODULE,
436
.open = iwl_dbgfs_fw_info_open,
437
.read = seq_read,
438
.llseek = seq_lseek,
439
.release = seq_release_private,
440
};
441
442
void iwl_fwrt_dbgfs_register(struct iwl_fw_runtime *fwrt,
443
struct dentry *dbgfs_dir)
444
{
445
INIT_DELAYED_WORK(&fwrt->timestamp.wk, iwl_fw_timestamp_marker_wk);
446
FWRT_DEBUGFS_ADD_FILE(timestamp_marker, dbgfs_dir, 0200);
447
FWRT_DEBUGFS_ADD_FILE(fw_info, dbgfs_dir, 0200);
448
FWRT_DEBUGFS_ADD_FILE(send_hcmd, dbgfs_dir, 0200);
449
FWRT_DEBUGFS_ADD_FILE(enabled_severities, dbgfs_dir, 0200);
450
FWRT_DEBUGFS_ADD_FILE(fw_dbg_collect, dbgfs_dir, 0200);
451
FWRT_DEBUGFS_ADD_FILE(fw_dbg_domain, dbgfs_dir, 0400);
452
FWRT_DEBUGFS_ADD_FILE(fw_ver, dbgfs_dir, 0400);
453
}
454
455