summaryrefslogtreecommitdiff
path: root/net/wireless/debugfs.c
blob: 40e49074e2eeb956a8f5e1c23563e61abd8900d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// SPDX-License-Identifier: GPL-2.0-only
/*
 * cfg80211 debugfs
 *
 * Copyright 2009	Luis R. Rodriguez <lrodriguez@atheros.com>
 * Copyright 2007	Johannes Berg <johannes@sipsolutions.net>
 * Copyright (C) 2023 Intel Corporation
 */

#include <linux/slab.h>
#include "core.h"
#include "debugfs.h"

#define DEBUGFS_READONLY_FILE(name, buflen, fmt, value...)		\
static ssize_t name## _read(struct file *file, char __user *userbuf,	\
			    size_t count, loff_t *ppos)			\
{									\
	struct wiphy *wiphy = file->private_data;			\
	char buf[buflen];						\
	int res;							\
									\
	res = scnprintf(buf, buflen, fmt "\n", ##value);		\
	return simple_read_from_buffer(userbuf, count, ppos, buf, res);	\
}									\
									\
static const struct file_operations name## _ops = {			\
	.read = name## _read,						\
	.open = simple_open,						\
	.llseek = generic_file_llseek,					\
}

DEBUGFS_READONLY_FILE(rts_threshold, 20, "%d",
		      wiphy->rts_threshold);
DEBUGFS_READONLY_FILE(fragmentation_threshold, 20, "%d",
		      wiphy->frag_threshold);
DEBUGFS_READONLY_FILE(short_retry_limit, 20, "%d",
		      wiphy->retry_short);
DEBUGFS_READONLY_FILE(long_retry_limit, 20, "%d",
		      wiphy->retry_long);

static int ht_print_chan(struct ieee80211_channel *chan,
			 char *buf, int buf_size, int offset)
{
	if (WARN_ON(offset > buf_size))
		return 0;

	if (chan->flags & IEEE80211_CHAN_DISABLED)
		return scnprintf(buf + offset,
				 buf_size - offset,
				 "%d Disabled\n",
				 chan->center_freq);

	return scnprintf(buf + offset,
			 buf_size - offset,
			 "%d HT40 %c%c\n",
			 chan->center_freq,
			 (chan->flags & IEEE80211_CHAN_NO_HT40MINUS) ?
				' ' : '-',
			 (chan->flags & IEEE80211_CHAN_NO_HT40PLUS) ?
				' ' : '+');
}

static ssize_t ht40allow_map_read(struct file *file,
				  char __user *user_buf,
				  size_t count, loff_t *ppos)
{
	struct wiphy *wiphy = file->private_data;
	char *buf;
	unsigned int offset = 0, buf_size = PAGE_SIZE, i;
	enum nl80211_band band;
	struct ieee80211_supported_band *sband;
	ssize_t r;

	buf = kzalloc(buf_size, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	for (band = 0; band < NUM_NL80211_BANDS; band++) {
		sband = wiphy->bands[band];
		if (!sband)
			continue;
		for (i = 0; i < sband->n_channels; i++)
			offset += ht_print_chan(&sband->channels[i],
						buf, buf_size, offset);
	}

	r = simple_read_from_buffer(user_buf, count, ppos, buf, offset);

	kfree(buf);

	return r;
}

static const struct file_operations ht40allow_map_ops = {
	.read = ht40allow_map_read,
	.open = simple_open,
	.llseek = default_llseek,
};

#define DEBUGFS_ADD(name)						\
	debugfs_create_file(#name, 0444, phyd, &rdev->wiphy, &name## _ops)

void cfg80211_debugfs_rdev_add(struct cfg80211_registered_device *rdev)
{
	struct dentry *phyd = rdev->wiphy.debugfsdir;

	DEBUGFS_ADD(rts_threshold);
	DEBUGFS_ADD(fragmentation_threshold);
	DEBUGFS_ADD(short_retry_limit);
	DEBUGFS_ADD(long_retry_limit);
	DEBUGFS_ADD(ht40allow_map);
}

struct debugfs_read_work {
	struct wiphy_work work;
	ssize_t (*handler)(struct wiphy *wiphy,
			   struct file *file,
			   char *buf,
			   size_t count,
			   void *data);
	struct wiphy *wiphy;
	struct file *file;
	char *buf;
	size_t bufsize;
	void *data;
	ssize_t ret;
	struct completion completion;
};

static void wiphy_locked_debugfs_read_work(struct wiphy *wiphy,
					   struct wiphy_work *work)
{
	struct debugfs_read_work *w = container_of(work, typeof(*w), work);

	w->ret = w->handler(w->wiphy, w->file, w->buf, w->bufsize, w->data);
	complete(&w->completion);
}

static void wiphy_locked_debugfs_read_cancel(struct dentry *dentry,
					     void *data)
{
	struct debugfs_read_work *w = data;

	wiphy_work_cancel(w->wiphy, &w->work);
	complete(&w->completion);
}

ssize_t wiphy_locked_debugfs_read(struct wiphy *wiphy, struct file *file,
				  char *buf, size_t bufsize,
				  char __user *userbuf, size_t count,
				  loff_t *ppos,
				  ssize_t (*handler)(struct wiphy *wiphy,
						     struct file *file,
						     char *buf,
						     size_t bufsize,
						     void *data),
				  void *data)
{
	struct debugfs_read_work work = {
		.handler = handler,
		.wiphy = wiphy,
		.file = file,
		.buf = buf,
		.bufsize = bufsize,
		.data = data,
		.ret = -ENODEV,
		.completion = COMPLETION_INITIALIZER_ONSTACK(work.completion),
	};
	struct debugfs_cancellation cancellation = {
		.cancel = wiphy_locked_debugfs_read_cancel,
		.cancel_data = &work,
	};

	/* don't leak stack data or whatever */
	memset(buf, 0, bufsize);

	wiphy_work_init(&work.work, wiphy_locked_debugfs_read_work);
	wiphy_work_queue(wiphy, &work.work);

	debugfs_enter_cancellation(file, &cancellation);
	wait_for_completion(&work.completion);
	debugfs_leave_cancellation(file, &cancellation);

	if (work.ret < 0)
		return work.ret;

	if (WARN_ON(work.ret > bufsize))
		return -EINVAL;

	return simple_read_from_buffer(userbuf, count, ppos, buf, work.ret);
}
EXPORT_SYMBOL_GPL(wiphy_locked_debugfs_read);

struct debugfs_write_work {
	struct wiphy_work work;
	ssize_t (*handler)(struct wiphy *wiphy,
			   struct file *file,
			   char *buf,
			   size_t count,
			   void *data);
	struct wiphy *wiphy;
	struct file *file;
	char *buf;
	size_t count;
	void *data;
	ssize_t ret;
	struct completion completion;
};

static void wiphy_locked_debugfs_write_work(struct wiphy *wiphy,
					    struct wiphy_work *work)
{
	struct debugfs_write_work *w = container_of(work, typeof(*w), work);

	w->ret = w->handler(w->wiphy, w->file, w->buf, w->count, w->data);
	complete(&w->completion);
}

static void wiphy_locked_debugfs_write_cancel(struct dentry *dentry,
					      void *data)
{
	struct debugfs_write_work *w = data;

	wiphy_work_cancel(w->wiphy, &w->work);
	complete(&w->completion);
}

ssize_t wiphy_locked_debugfs_write(struct wiphy *wiphy,
				   struct file *file, char *buf, size_t bufsize,
				   const char __user *userbuf, size_t count,
				   ssize_t (*handler)(struct wiphy *wiphy,
						      struct file *file,
						      char *buf,
						      size_t count,
						      void *data),
				   void *data)
{
	struct debugfs_write_work work = {
		.handler = handler,
		.wiphy = wiphy,
		.file = file,
		.buf = buf,
		.count = count,
		.data = data,
		.ret = -ENODEV,
		.completion = COMPLETION_INITIALIZER_ONSTACK(work.completion),
	};
	struct debugfs_cancellation cancellation = {
		.cancel = wiphy_locked_debugfs_write_cancel,
		.cancel_data = &work,
	};

	/* mostly used for strings so enforce NUL-termination for safety */
	if (count >= bufsize)
		return -EINVAL;

	memset(buf, 0, bufsize);

	if (copy_from_user(buf, userbuf, count))
		return -EFAULT;

	wiphy_work_init(&work.work, wiphy_locked_debugfs_write_work);
	wiphy_work_queue(wiphy, &work.work);

	debugfs_enter_cancellation(file, &cancellation);
	wait_for_completion(&work.completion);
	debugfs_leave_cancellation(file, &cancellation);

	return work.ret;
}
EXPORT_SYMBOL_GPL(wiphy_locked_debugfs_write);