xref: /linux-6.15/net/wireless/debugfs.c (revision b590b9ae)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21ac61302SLuis R. Rodriguez /*
31ac61302SLuis R. Rodriguez  * cfg80211 debugfs
41ac61302SLuis R. Rodriguez  *
51ac61302SLuis R. Rodriguez  * Copyright 2009	Luis R. Rodriguez <[email protected]>
61ac61302SLuis R. Rodriguez  * Copyright 2007	Johannes Berg <[email protected]>
7*b590b9aeSJohannes Berg  * Copyright (C) 2023 Intel Corporation
81ac61302SLuis R. Rodriguez  */
91ac61302SLuis R. Rodriguez 
105a0e3ad6STejun Heo #include <linux/slab.h>
111ac61302SLuis R. Rodriguez #include "core.h"
121ac61302SLuis R. Rodriguez #include "debugfs.h"
131ac61302SLuis R. Rodriguez 
141ac61302SLuis R. Rodriguez #define DEBUGFS_READONLY_FILE(name, buflen, fmt, value...)		\
151ac61302SLuis R. Rodriguez static ssize_t name## _read(struct file *file, char __user *userbuf,	\
161ac61302SLuis R. Rodriguez 			    size_t count, loff_t *ppos)			\
171ac61302SLuis R. Rodriguez {									\
181ac61302SLuis R. Rodriguez 	struct wiphy *wiphy = file->private_data;			\
191ac61302SLuis R. Rodriguez 	char buf[buflen];						\
201ac61302SLuis R. Rodriguez 	int res;							\
211ac61302SLuis R. Rodriguez 									\
221ac61302SLuis R. Rodriguez 	res = scnprintf(buf, buflen, fmt "\n", ##value);		\
231ac61302SLuis R. Rodriguez 	return simple_read_from_buffer(userbuf, count, ppos, buf, res);	\
241ac61302SLuis R. Rodriguez }									\
251ac61302SLuis R. Rodriguez 									\
261ac61302SLuis R. Rodriguez static const struct file_operations name## _ops = {			\
271ac61302SLuis R. Rodriguez 	.read = name## _read,						\
28234e3405SStephen Boyd 	.open = simple_open,						\
292b18ab36SArnd Bergmann 	.llseek = generic_file_llseek,					\
30b699b71dSPichugin Dmitry }
311ac61302SLuis R. Rodriguez 
321ac61302SLuis R. Rodriguez DEBUGFS_READONLY_FILE(rts_threshold, 20, "%d",
33b699b71dSPichugin Dmitry 		      wiphy->rts_threshold);
341ac61302SLuis R. Rodriguez DEBUGFS_READONLY_FILE(fragmentation_threshold, 20, "%d",
351ac61302SLuis R. Rodriguez 		      wiphy->frag_threshold);
361ac61302SLuis R. Rodriguez DEBUGFS_READONLY_FILE(short_retry_limit, 20, "%d",
37b699b71dSPichugin Dmitry 		      wiphy->retry_short);
381ac61302SLuis R. Rodriguez DEBUGFS_READONLY_FILE(long_retry_limit, 20, "%d",
391ac61302SLuis R. Rodriguez 		      wiphy->retry_long);
401ac61302SLuis R. Rodriguez 
ht_print_chan(struct ieee80211_channel * chan,char * buf,int buf_size,int offset)4180a3511dSLuis R. Rodriguez static int ht_print_chan(struct ieee80211_channel *chan,
4280a3511dSLuis R. Rodriguez 			 char *buf, int buf_size, int offset)
4380a3511dSLuis R. Rodriguez {
4480a3511dSLuis R. Rodriguez 	if (WARN_ON(offset > buf_size))
4580a3511dSLuis R. Rodriguez 		return 0;
4680a3511dSLuis R. Rodriguez 
4780a3511dSLuis R. Rodriguez 	if (chan->flags & IEEE80211_CHAN_DISABLED)
48f364ef99SEliad Peller 		return scnprintf(buf + offset,
4980a3511dSLuis R. Rodriguez 				 buf_size - offset,
5080a3511dSLuis R. Rodriguez 				 "%d Disabled\n",
5180a3511dSLuis R. Rodriguez 				 chan->center_freq);
5280a3511dSLuis R. Rodriguez 
53f364ef99SEliad Peller 	return scnprintf(buf + offset,
5480a3511dSLuis R. Rodriguez 			 buf_size - offset,
5580a3511dSLuis R. Rodriguez 			 "%d HT40 %c%c\n",
5680a3511dSLuis R. Rodriguez 			 chan->center_freq,
57f364ef99SEliad Peller 			 (chan->flags & IEEE80211_CHAN_NO_HT40MINUS) ?
58f364ef99SEliad Peller 				' ' : '-',
59f364ef99SEliad Peller 			 (chan->flags & IEEE80211_CHAN_NO_HT40PLUS) ?
60f364ef99SEliad Peller 				' ' : '+');
6180a3511dSLuis R. Rodriguez }
6280a3511dSLuis R. Rodriguez 
ht40allow_map_read(struct file * file,char __user * user_buf,size_t count,loff_t * ppos)6380a3511dSLuis R. Rodriguez static ssize_t ht40allow_map_read(struct file *file,
6480a3511dSLuis R. Rodriguez 				  char __user *user_buf,
6580a3511dSLuis R. Rodriguez 				  size_t count, loff_t *ppos)
6680a3511dSLuis R. Rodriguez {
6780a3511dSLuis R. Rodriguez 	struct wiphy *wiphy = file->private_data;
6880a3511dSLuis R. Rodriguez 	char *buf;
69d776763fSDan Carpenter 	unsigned int offset = 0, buf_size = PAGE_SIZE, i;
7057fbcce3SJohannes Berg 	enum nl80211_band band;
7180a3511dSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
72d776763fSDan Carpenter 	ssize_t r;
7380a3511dSLuis R. Rodriguez 
7480a3511dSLuis R. Rodriguez 	buf = kzalloc(buf_size, GFP_KERNEL);
7580a3511dSLuis R. Rodriguez 	if (!buf)
7680a3511dSLuis R. Rodriguez 		return -ENOMEM;
7780a3511dSLuis R. Rodriguez 
7857fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++) {
7980a3511dSLuis R. Rodriguez 		sband = wiphy->bands[band];
8080a3511dSLuis R. Rodriguez 		if (!sband)
8180a3511dSLuis R. Rodriguez 			continue;
8280a3511dSLuis R. Rodriguez 		for (i = 0; i < sband->n_channels; i++)
8380a3511dSLuis R. Rodriguez 			offset += ht_print_chan(&sband->channels[i],
8480a3511dSLuis R. Rodriguez 						buf, buf_size, offset);
8580a3511dSLuis R. Rodriguez 	}
8680a3511dSLuis R. Rodriguez 
8780a3511dSLuis R. Rodriguez 	r = simple_read_from_buffer(user_buf, count, ppos, buf, offset);
8880a3511dSLuis R. Rodriguez 
8980a3511dSLuis R. Rodriguez 	kfree(buf);
9080a3511dSLuis R. Rodriguez 
9180a3511dSLuis R. Rodriguez 	return r;
9280a3511dSLuis R. Rodriguez }
9380a3511dSLuis R. Rodriguez 
9480a3511dSLuis R. Rodriguez static const struct file_operations ht40allow_map_ops = {
9580a3511dSLuis R. Rodriguez 	.read = ht40allow_map_read,
96234e3405SStephen Boyd 	.open = simple_open,
976038f373SArnd Bergmann 	.llseek = default_llseek,
9880a3511dSLuis R. Rodriguez };
9980a3511dSLuis R. Rodriguez 
1001ac61302SLuis R. Rodriguez #define DEBUGFS_ADD(name)						\
101b699b71dSPichugin Dmitry 	debugfs_create_file(#name, 0444, phyd, &rdev->wiphy, &name## _ops)
1021ac61302SLuis R. Rodriguez 
cfg80211_debugfs_rdev_add(struct cfg80211_registered_device * rdev)10379c97e97SJohannes Berg void cfg80211_debugfs_rdev_add(struct cfg80211_registered_device *rdev)
1041ac61302SLuis R. Rodriguez {
10579c97e97SJohannes Berg 	struct dentry *phyd = rdev->wiphy.debugfsdir;
1061ac61302SLuis R. Rodriguez 
1071ac61302SLuis R. Rodriguez 	DEBUGFS_ADD(rts_threshold);
1081ac61302SLuis R. Rodriguez 	DEBUGFS_ADD(fragmentation_threshold);
1091ac61302SLuis R. Rodriguez 	DEBUGFS_ADD(short_retry_limit);
1101ac61302SLuis R. Rodriguez 	DEBUGFS_ADD(long_retry_limit);
11180a3511dSLuis R. Rodriguez 	DEBUGFS_ADD(ht40allow_map);
1121ac61302SLuis R. Rodriguez }
113*b590b9aeSJohannes Berg 
114*b590b9aeSJohannes Berg struct debugfs_read_work {
115*b590b9aeSJohannes Berg 	struct wiphy_work work;
116*b590b9aeSJohannes Berg 	ssize_t (*handler)(struct wiphy *wiphy,
117*b590b9aeSJohannes Berg 			   struct file *file,
118*b590b9aeSJohannes Berg 			   char *buf,
119*b590b9aeSJohannes Berg 			   size_t count,
120*b590b9aeSJohannes Berg 			   void *data);
121*b590b9aeSJohannes Berg 	struct wiphy *wiphy;
122*b590b9aeSJohannes Berg 	struct file *file;
123*b590b9aeSJohannes Berg 	char *buf;
124*b590b9aeSJohannes Berg 	size_t bufsize;
125*b590b9aeSJohannes Berg 	void *data;
126*b590b9aeSJohannes Berg 	ssize_t ret;
127*b590b9aeSJohannes Berg 	struct completion completion;
128*b590b9aeSJohannes Berg };
129*b590b9aeSJohannes Berg 
wiphy_locked_debugfs_read_work(struct wiphy * wiphy,struct wiphy_work * work)130*b590b9aeSJohannes Berg static void wiphy_locked_debugfs_read_work(struct wiphy *wiphy,
131*b590b9aeSJohannes Berg 					   struct wiphy_work *work)
132*b590b9aeSJohannes Berg {
133*b590b9aeSJohannes Berg 	struct debugfs_read_work *w = container_of(work, typeof(*w), work);
134*b590b9aeSJohannes Berg 
135*b590b9aeSJohannes Berg 	w->ret = w->handler(w->wiphy, w->file, w->buf, w->bufsize, w->data);
136*b590b9aeSJohannes Berg 	complete(&w->completion);
137*b590b9aeSJohannes Berg }
138*b590b9aeSJohannes Berg 
wiphy_locked_debugfs_read_cancel(struct dentry * dentry,void * data)139*b590b9aeSJohannes Berg static void wiphy_locked_debugfs_read_cancel(struct dentry *dentry,
140*b590b9aeSJohannes Berg 					     void *data)
141*b590b9aeSJohannes Berg {
142*b590b9aeSJohannes Berg 	struct debugfs_read_work *w = data;
143*b590b9aeSJohannes Berg 
144*b590b9aeSJohannes Berg 	wiphy_work_cancel(w->wiphy, &w->work);
145*b590b9aeSJohannes Berg 	complete(&w->completion);
146*b590b9aeSJohannes Berg }
147*b590b9aeSJohannes Berg 
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)148*b590b9aeSJohannes Berg ssize_t wiphy_locked_debugfs_read(struct wiphy *wiphy, struct file *file,
149*b590b9aeSJohannes Berg 				  char *buf, size_t bufsize,
150*b590b9aeSJohannes Berg 				  char __user *userbuf, size_t count,
151*b590b9aeSJohannes Berg 				  loff_t *ppos,
152*b590b9aeSJohannes Berg 				  ssize_t (*handler)(struct wiphy *wiphy,
153*b590b9aeSJohannes Berg 						     struct file *file,
154*b590b9aeSJohannes Berg 						     char *buf,
155*b590b9aeSJohannes Berg 						     size_t bufsize,
156*b590b9aeSJohannes Berg 						     void *data),
157*b590b9aeSJohannes Berg 				  void *data)
158*b590b9aeSJohannes Berg {
159*b590b9aeSJohannes Berg 	struct debugfs_read_work work = {
160*b590b9aeSJohannes Berg 		.handler = handler,
161*b590b9aeSJohannes Berg 		.wiphy = wiphy,
162*b590b9aeSJohannes Berg 		.file = file,
163*b590b9aeSJohannes Berg 		.buf = buf,
164*b590b9aeSJohannes Berg 		.bufsize = bufsize,
165*b590b9aeSJohannes Berg 		.data = data,
166*b590b9aeSJohannes Berg 		.ret = -ENODEV,
167*b590b9aeSJohannes Berg 		.completion = COMPLETION_INITIALIZER_ONSTACK(work.completion),
168*b590b9aeSJohannes Berg 	};
169*b590b9aeSJohannes Berg 	struct debugfs_cancellation cancellation = {
170*b590b9aeSJohannes Berg 		.cancel = wiphy_locked_debugfs_read_cancel,
171*b590b9aeSJohannes Berg 		.cancel_data = &work,
172*b590b9aeSJohannes Berg 	};
173*b590b9aeSJohannes Berg 
174*b590b9aeSJohannes Berg 	/* don't leak stack data or whatever */
175*b590b9aeSJohannes Berg 	memset(buf, 0, bufsize);
176*b590b9aeSJohannes Berg 
177*b590b9aeSJohannes Berg 	wiphy_work_init(&work.work, wiphy_locked_debugfs_read_work);
178*b590b9aeSJohannes Berg 	wiphy_work_queue(wiphy, &work.work);
179*b590b9aeSJohannes Berg 
180*b590b9aeSJohannes Berg 	debugfs_enter_cancellation(file, &cancellation);
181*b590b9aeSJohannes Berg 	wait_for_completion(&work.completion);
182*b590b9aeSJohannes Berg 	debugfs_leave_cancellation(file, &cancellation);
183*b590b9aeSJohannes Berg 
184*b590b9aeSJohannes Berg 	if (work.ret < 0)
185*b590b9aeSJohannes Berg 		return work.ret;
186*b590b9aeSJohannes Berg 
187*b590b9aeSJohannes Berg 	if (WARN_ON(work.ret > bufsize))
188*b590b9aeSJohannes Berg 		return -EINVAL;
189*b590b9aeSJohannes Berg 
190*b590b9aeSJohannes Berg 	return simple_read_from_buffer(userbuf, count, ppos, buf, work.ret);
191*b590b9aeSJohannes Berg }
192*b590b9aeSJohannes Berg EXPORT_SYMBOL_GPL(wiphy_locked_debugfs_read);
193*b590b9aeSJohannes Berg 
194*b590b9aeSJohannes Berg struct debugfs_write_work {
195*b590b9aeSJohannes Berg 	struct wiphy_work work;
196*b590b9aeSJohannes Berg 	ssize_t (*handler)(struct wiphy *wiphy,
197*b590b9aeSJohannes Berg 			   struct file *file,
198*b590b9aeSJohannes Berg 			   char *buf,
199*b590b9aeSJohannes Berg 			   size_t count,
200*b590b9aeSJohannes Berg 			   void *data);
201*b590b9aeSJohannes Berg 	struct wiphy *wiphy;
202*b590b9aeSJohannes Berg 	struct file *file;
203*b590b9aeSJohannes Berg 	char *buf;
204*b590b9aeSJohannes Berg 	size_t count;
205*b590b9aeSJohannes Berg 	void *data;
206*b590b9aeSJohannes Berg 	ssize_t ret;
207*b590b9aeSJohannes Berg 	struct completion completion;
208*b590b9aeSJohannes Berg };
209*b590b9aeSJohannes Berg 
wiphy_locked_debugfs_write_work(struct wiphy * wiphy,struct wiphy_work * work)210*b590b9aeSJohannes Berg static void wiphy_locked_debugfs_write_work(struct wiphy *wiphy,
211*b590b9aeSJohannes Berg 					    struct wiphy_work *work)
212*b590b9aeSJohannes Berg {
213*b590b9aeSJohannes Berg 	struct debugfs_write_work *w = container_of(work, typeof(*w), work);
214*b590b9aeSJohannes Berg 
215*b590b9aeSJohannes Berg 	w->ret = w->handler(w->wiphy, w->file, w->buf, w->count, w->data);
216*b590b9aeSJohannes Berg 	complete(&w->completion);
217*b590b9aeSJohannes Berg }
218*b590b9aeSJohannes Berg 
wiphy_locked_debugfs_write_cancel(struct dentry * dentry,void * data)219*b590b9aeSJohannes Berg static void wiphy_locked_debugfs_write_cancel(struct dentry *dentry,
220*b590b9aeSJohannes Berg 					      void *data)
221*b590b9aeSJohannes Berg {
222*b590b9aeSJohannes Berg 	struct debugfs_write_work *w = data;
223*b590b9aeSJohannes Berg 
224*b590b9aeSJohannes Berg 	wiphy_work_cancel(w->wiphy, &w->work);
225*b590b9aeSJohannes Berg 	complete(&w->completion);
226*b590b9aeSJohannes Berg }
227*b590b9aeSJohannes Berg 
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)228*b590b9aeSJohannes Berg ssize_t wiphy_locked_debugfs_write(struct wiphy *wiphy,
229*b590b9aeSJohannes Berg 				   struct file *file, char *buf, size_t bufsize,
230*b590b9aeSJohannes Berg 				   const char __user *userbuf, size_t count,
231*b590b9aeSJohannes Berg 				   ssize_t (*handler)(struct wiphy *wiphy,
232*b590b9aeSJohannes Berg 						      struct file *file,
233*b590b9aeSJohannes Berg 						      char *buf,
234*b590b9aeSJohannes Berg 						      size_t count,
235*b590b9aeSJohannes Berg 						      void *data),
236*b590b9aeSJohannes Berg 				   void *data)
237*b590b9aeSJohannes Berg {
238*b590b9aeSJohannes Berg 	struct debugfs_write_work work = {
239*b590b9aeSJohannes Berg 		.handler = handler,
240*b590b9aeSJohannes Berg 		.wiphy = wiphy,
241*b590b9aeSJohannes Berg 		.file = file,
242*b590b9aeSJohannes Berg 		.buf = buf,
243*b590b9aeSJohannes Berg 		.count = count,
244*b590b9aeSJohannes Berg 		.data = data,
245*b590b9aeSJohannes Berg 		.ret = -ENODEV,
246*b590b9aeSJohannes Berg 		.completion = COMPLETION_INITIALIZER_ONSTACK(work.completion),
247*b590b9aeSJohannes Berg 	};
248*b590b9aeSJohannes Berg 	struct debugfs_cancellation cancellation = {
249*b590b9aeSJohannes Berg 		.cancel = wiphy_locked_debugfs_write_cancel,
250*b590b9aeSJohannes Berg 		.cancel_data = &work,
251*b590b9aeSJohannes Berg 	};
252*b590b9aeSJohannes Berg 
253*b590b9aeSJohannes Berg 	/* mostly used for strings so enforce NUL-termination for safety */
254*b590b9aeSJohannes Berg 	if (count >= bufsize)
255*b590b9aeSJohannes Berg 		return -EINVAL;
256*b590b9aeSJohannes Berg 
257*b590b9aeSJohannes Berg 	memset(buf, 0, bufsize);
258*b590b9aeSJohannes Berg 
259*b590b9aeSJohannes Berg 	if (copy_from_user(buf, userbuf, count))
260*b590b9aeSJohannes Berg 		return -EFAULT;
261*b590b9aeSJohannes Berg 
262*b590b9aeSJohannes Berg 	wiphy_work_init(&work.work, wiphy_locked_debugfs_write_work);
263*b590b9aeSJohannes Berg 	wiphy_work_queue(wiphy, &work.work);
264*b590b9aeSJohannes Berg 
265*b590b9aeSJohannes Berg 	debugfs_enter_cancellation(file, &cancellation);
266*b590b9aeSJohannes Berg 	wait_for_completion(&work.completion);
267*b590b9aeSJohannes Berg 	debugfs_leave_cancellation(file, &cancellation);
268*b590b9aeSJohannes Berg 
269*b590b9aeSJohannes Berg 	return work.ret;
270*b590b9aeSJohannes Berg }
271*b590b9aeSJohannes Berg EXPORT_SYMBOL_GPL(wiphy_locked_debugfs_write);
272