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