1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* net/atm/proc.c - ATM /proc interface |
3 | * |
4 | * Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA |
5 | * |
6 | * seq_file api usage by romieu@fr.zoreil.com |
7 | * |
8 | * Evaluating the efficiency of the whole thing if left as an exercise to |
9 | * the reader. |
10 | */ |
11 | |
12 | #include <linux/module.h> /* for EXPORT_SYMBOL */ |
13 | #include <linux/string.h> |
14 | #include <linux/types.h> |
15 | #include <linux/mm.h> |
16 | #include <linux/fs.h> |
17 | #include <linux/stat.h> |
18 | #include <linux/proc_fs.h> |
19 | #include <linux/seq_file.h> |
20 | #include <linux/errno.h> |
21 | #include <linux/atm.h> |
22 | #include <linux/atmdev.h> |
23 | #include <linux/netdevice.h> |
24 | #include <linux/atmclip.h> |
25 | #include <linux/init.h> /* for __init */ |
26 | #include <linux/slab.h> |
27 | #include <net/net_namespace.h> |
28 | #include <net/atmclip.h> |
29 | #include <linux/uaccess.h> |
30 | #include <linux/param.h> /* for HZ */ |
31 | #include <linux/atomic.h> |
32 | #include "resources.h" |
33 | #include "common.h" /* atm_proc_init prototype */ |
34 | #include "signaling.h" /* to get sigd - ugly too */ |
35 | |
36 | static ssize_t proc_dev_atm_read(struct file *file, char __user *buf, |
37 | size_t count, loff_t *pos); |
38 | |
39 | static const struct proc_ops atm_dev_proc_ops = { |
40 | .proc_read = proc_dev_atm_read, |
41 | .proc_lseek = noop_llseek, |
42 | }; |
43 | |
44 | static void add_stats(struct seq_file *seq, const char *aal, |
45 | const struct k_atm_aal_stats *stats) |
46 | { |
47 | seq_printf(m: seq, fmt: "%s ( %d %d %d %d %d )" , aal, |
48 | atomic_read(v: &stats->tx), atomic_read(v: &stats->tx_err), |
49 | atomic_read(v: &stats->rx), atomic_read(v: &stats->rx_err), |
50 | atomic_read(v: &stats->rx_drop)); |
51 | } |
52 | |
53 | static void atm_dev_info(struct seq_file *seq, const struct atm_dev *dev) |
54 | { |
55 | int i; |
56 | |
57 | seq_printf(m: seq, fmt: "%3d %-8s" , dev->number, dev->type); |
58 | for (i = 0; i < ESI_LEN; i++) |
59 | seq_printf(m: seq, fmt: "%02x" , dev->esi[i]); |
60 | seq_puts(m: seq, s: " " ); |
61 | add_stats(seq, aal: "0" , stats: &dev->stats.aal0); |
62 | seq_puts(m: seq, s: " " ); |
63 | add_stats(seq, aal: "5" , stats: &dev->stats.aal5); |
64 | seq_printf(m: seq, fmt: "\t[%d]" , refcount_read(r: &dev->refcnt)); |
65 | seq_putc(m: seq, c: '\n'); |
66 | } |
67 | |
68 | struct vcc_state { |
69 | int bucket; |
70 | struct sock *sk; |
71 | }; |
72 | |
73 | static inline int compare_family(struct sock *sk, int family) |
74 | { |
75 | return !family || (sk->sk_family == family); |
76 | } |
77 | |
78 | static int __vcc_walk(struct sock **sock, int family, int *bucket, loff_t l) |
79 | { |
80 | struct sock *sk = *sock; |
81 | |
82 | if (sk == SEQ_START_TOKEN) { |
83 | for (*bucket = 0; *bucket < VCC_HTABLE_SIZE; ++*bucket) { |
84 | struct hlist_head *head = &vcc_hash[*bucket]; |
85 | |
86 | sk = hlist_empty(h: head) ? NULL : __sk_head(head); |
87 | if (sk) |
88 | break; |
89 | } |
90 | l--; |
91 | } |
92 | try_again: |
93 | for (; sk; sk = sk_next(sk)) { |
94 | l -= compare_family(sk, family); |
95 | if (l < 0) |
96 | goto out; |
97 | } |
98 | if (!sk && ++*bucket < VCC_HTABLE_SIZE) { |
99 | sk = sk_head(head: &vcc_hash[*bucket]); |
100 | goto try_again; |
101 | } |
102 | sk = SEQ_START_TOKEN; |
103 | out: |
104 | *sock = sk; |
105 | return (l < 0); |
106 | } |
107 | |
108 | static inline void *vcc_walk(struct seq_file *seq, loff_t l) |
109 | { |
110 | struct vcc_state *state = seq->private; |
111 | int family = (uintptr_t)(pde_data(inode: file_inode(f: seq->file))); |
112 | |
113 | return __vcc_walk(sock: &state->sk, family, bucket: &state->bucket, l) ? |
114 | state : NULL; |
115 | } |
116 | |
117 | static void *vcc_seq_start(struct seq_file *seq, loff_t *pos) |
118 | __acquires(vcc_sklist_lock) |
119 | { |
120 | struct vcc_state *state = seq->private; |
121 | loff_t left = *pos; |
122 | |
123 | read_lock(&vcc_sklist_lock); |
124 | state->sk = SEQ_START_TOKEN; |
125 | return left ? vcc_walk(seq, l: left) : SEQ_START_TOKEN; |
126 | } |
127 | |
128 | static void vcc_seq_stop(struct seq_file *seq, void *v) |
129 | __releases(vcc_sklist_lock) |
130 | { |
131 | read_unlock(&vcc_sklist_lock); |
132 | } |
133 | |
134 | static void *vcc_seq_next(struct seq_file *seq, void *v, loff_t *pos) |
135 | { |
136 | v = vcc_walk(seq, l: 1); |
137 | (*pos)++; |
138 | return v; |
139 | } |
140 | |
141 | static void pvc_info(struct seq_file *seq, struct atm_vcc *vcc) |
142 | { |
143 | static const char *const class_name[] = { |
144 | "off" , "UBR" , "CBR" , "VBR" , "ABR" }; |
145 | static const char *const aal_name[] = { |
146 | "---" , "1" , "2" , "3/4" , /* 0- 3 */ |
147 | "???" , "5" , "???" , "???" , /* 4- 7 */ |
148 | "???" , "???" , "???" , "???" , /* 8-11 */ |
149 | "???" , "0" , "???" , "???" }; /* 12-15 */ |
150 | |
151 | seq_printf(m: seq, fmt: "%3d %3d %5d %-3s %7d %-5s %7d %-6s" , |
152 | vcc->dev->number, vcc->vpi, vcc->vci, |
153 | vcc->qos.aal >= ARRAY_SIZE(aal_name) ? "err" : |
154 | aal_name[vcc->qos.aal], vcc->qos.rxtp.min_pcr, |
155 | class_name[vcc->qos.rxtp.traffic_class], |
156 | vcc->qos.txtp.min_pcr, |
157 | class_name[vcc->qos.txtp.traffic_class]); |
158 | if (test_bit(ATM_VF_IS_CLIP, &vcc->flags)) { |
159 | struct clip_vcc *clip_vcc = CLIP_VCC(vcc); |
160 | struct net_device *dev; |
161 | |
162 | dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev : NULL; |
163 | seq_printf(m: seq, fmt: "CLIP, Itf:%s, Encap:" , |
164 | dev ? dev->name : "none?" ); |
165 | seq_printf(m: seq, fmt: "%s" , clip_vcc->encap ? "LLC/SNAP" : "None" ); |
166 | } |
167 | seq_putc(m: seq, c: '\n'); |
168 | } |
169 | |
170 | static const char *vcc_state(struct atm_vcc *vcc) |
171 | { |
172 | static const char *const map[] = { ATM_VS2TXT_MAP }; |
173 | |
174 | return map[ATM_VF2VS(vcc->flags)]; |
175 | } |
176 | |
177 | static void vcc_info(struct seq_file *seq, struct atm_vcc *vcc) |
178 | { |
179 | struct sock *sk = sk_atm(vcc); |
180 | |
181 | seq_printf(m: seq, fmt: "%pK " , vcc); |
182 | if (!vcc->dev) |
183 | seq_printf(m: seq, fmt: "Unassigned " ); |
184 | else |
185 | seq_printf(m: seq, fmt: "%3d %3d %5d " , vcc->dev->number, vcc->vpi, |
186 | vcc->vci); |
187 | switch (sk->sk_family) { |
188 | case AF_ATMPVC: |
189 | seq_printf(m: seq, fmt: "PVC" ); |
190 | break; |
191 | case AF_ATMSVC: |
192 | seq_printf(m: seq, fmt: "SVC" ); |
193 | break; |
194 | default: |
195 | seq_printf(m: seq, fmt: "%3d" , sk->sk_family); |
196 | } |
197 | seq_printf(m: seq, fmt: " %04lx %5d %7d/%7d %7d/%7d [%d]\n" , |
198 | vcc->flags, sk->sk_err, |
199 | sk_wmem_alloc_get(sk), sk->sk_sndbuf, |
200 | sk_rmem_alloc_get(sk), sk->sk_rcvbuf, |
201 | refcount_read(r: &sk->sk_refcnt)); |
202 | } |
203 | |
204 | static void svc_info(struct seq_file *seq, struct atm_vcc *vcc) |
205 | { |
206 | if (!vcc->dev) |
207 | seq_printf(m: seq, fmt: sizeof(void *) == 4 ? |
208 | "N/A@%pK%10s" : "N/A@%pK%2s" , vcc, "" ); |
209 | else |
210 | seq_printf(m: seq, fmt: "%3d %3d %5d " , |
211 | vcc->dev->number, vcc->vpi, vcc->vci); |
212 | seq_printf(m: seq, fmt: "%-10s " , vcc_state(vcc)); |
213 | seq_printf(m: seq, fmt: "%s%s" , vcc->remote.sas_addr.pub, |
214 | *vcc->remote.sas_addr.pub && *vcc->remote.sas_addr.prv ? "+" : "" ); |
215 | if (*vcc->remote.sas_addr.prv) { |
216 | int i; |
217 | |
218 | for (i = 0; i < ATM_ESA_LEN; i++) |
219 | seq_printf(m: seq, fmt: "%02x" , vcc->remote.sas_addr.prv[i]); |
220 | } |
221 | seq_putc(m: seq, c: '\n'); |
222 | } |
223 | |
224 | static int atm_dev_seq_show(struct seq_file *seq, void *v) |
225 | { |
226 | static char atm_dev_banner[] = |
227 | "Itf Type ESI/\"MAC\"addr " |
228 | "AAL(TX,err,RX,err,drop) ... [refcnt]\n" ; |
229 | |
230 | if (v == &atm_devs) |
231 | seq_puts(m: seq, s: atm_dev_banner); |
232 | else { |
233 | struct atm_dev *dev = list_entry(v, struct atm_dev, dev_list); |
234 | |
235 | atm_dev_info(seq, dev); |
236 | } |
237 | return 0; |
238 | } |
239 | |
240 | static const struct seq_operations atm_dev_seq_ops = { |
241 | .start = atm_dev_seq_start, |
242 | .next = atm_dev_seq_next, |
243 | .stop = atm_dev_seq_stop, |
244 | .show = atm_dev_seq_show, |
245 | }; |
246 | |
247 | static int pvc_seq_show(struct seq_file *seq, void *v) |
248 | { |
249 | static char atm_pvc_banner[] = |
250 | "Itf VPI VCI AAL RX(PCR,Class) TX(PCR,Class)\n" ; |
251 | |
252 | if (v == SEQ_START_TOKEN) |
253 | seq_puts(m: seq, s: atm_pvc_banner); |
254 | else { |
255 | struct vcc_state *state = seq->private; |
256 | struct atm_vcc *vcc = atm_sk(sk: state->sk); |
257 | |
258 | pvc_info(seq, vcc); |
259 | } |
260 | return 0; |
261 | } |
262 | |
263 | static const struct seq_operations pvc_seq_ops = { |
264 | .start = vcc_seq_start, |
265 | .next = vcc_seq_next, |
266 | .stop = vcc_seq_stop, |
267 | .show = pvc_seq_show, |
268 | }; |
269 | |
270 | static int vcc_seq_show(struct seq_file *seq, void *v) |
271 | { |
272 | if (v == SEQ_START_TOKEN) { |
273 | seq_printf(m: seq, fmt: sizeof(void *) == 4 ? "%-8s%s" : "%-16s%s" , |
274 | "Address " , "Itf VPI VCI Fam Flags Reply " |
275 | "Send buffer Recv buffer [refcnt]\n" ); |
276 | } else { |
277 | struct vcc_state *state = seq->private; |
278 | struct atm_vcc *vcc = atm_sk(sk: state->sk); |
279 | |
280 | vcc_info(seq, vcc); |
281 | } |
282 | return 0; |
283 | } |
284 | |
285 | static const struct seq_operations vcc_seq_ops = { |
286 | .start = vcc_seq_start, |
287 | .next = vcc_seq_next, |
288 | .stop = vcc_seq_stop, |
289 | .show = vcc_seq_show, |
290 | }; |
291 | |
292 | static int svc_seq_show(struct seq_file *seq, void *v) |
293 | { |
294 | static const char atm_svc_banner[] = |
295 | "Itf VPI VCI State Remote\n" ; |
296 | |
297 | if (v == SEQ_START_TOKEN) |
298 | seq_puts(m: seq, s: atm_svc_banner); |
299 | else { |
300 | struct vcc_state *state = seq->private; |
301 | struct atm_vcc *vcc = atm_sk(sk: state->sk); |
302 | |
303 | svc_info(seq, vcc); |
304 | } |
305 | return 0; |
306 | } |
307 | |
308 | static const struct seq_operations svc_seq_ops = { |
309 | .start = vcc_seq_start, |
310 | .next = vcc_seq_next, |
311 | .stop = vcc_seq_stop, |
312 | .show = svc_seq_show, |
313 | }; |
314 | |
315 | static ssize_t proc_dev_atm_read(struct file *file, char __user *buf, |
316 | size_t count, loff_t *pos) |
317 | { |
318 | struct atm_dev *dev; |
319 | unsigned long page; |
320 | int length; |
321 | |
322 | if (count == 0) |
323 | return 0; |
324 | page = get_zeroed_page(GFP_KERNEL); |
325 | if (!page) |
326 | return -ENOMEM; |
327 | dev = pde_data(inode: file_inode(f: file)); |
328 | if (!dev->ops->proc_read) |
329 | length = -EINVAL; |
330 | else { |
331 | length = dev->ops->proc_read(dev, pos, (char *)page); |
332 | if (length > count) |
333 | length = -EINVAL; |
334 | } |
335 | if (length >= 0) { |
336 | if (copy_to_user(to: buf, from: (char *)page, n: length)) |
337 | length = -EFAULT; |
338 | (*pos)++; |
339 | } |
340 | free_page(page); |
341 | return length; |
342 | } |
343 | |
344 | struct proc_dir_entry *atm_proc_root; |
345 | EXPORT_SYMBOL(atm_proc_root); |
346 | |
347 | |
348 | int atm_proc_dev_register(struct atm_dev *dev) |
349 | { |
350 | int error; |
351 | |
352 | /* No proc info */ |
353 | if (!dev->ops->proc_read) |
354 | return 0; |
355 | |
356 | error = -ENOMEM; |
357 | dev->proc_name = kasprintf(GFP_KERNEL, fmt: "%s:%d" , dev->type, dev->number); |
358 | if (!dev->proc_name) |
359 | goto err_out; |
360 | |
361 | dev->proc_entry = proc_create_data(dev->proc_name, 0, atm_proc_root, |
362 | &atm_dev_proc_ops, dev); |
363 | if (!dev->proc_entry) |
364 | goto err_free_name; |
365 | return 0; |
366 | |
367 | err_free_name: |
368 | kfree(objp: dev->proc_name); |
369 | err_out: |
370 | return error; |
371 | } |
372 | |
373 | void atm_proc_dev_deregister(struct atm_dev *dev) |
374 | { |
375 | if (!dev->ops->proc_read) |
376 | return; |
377 | |
378 | remove_proc_entry(dev->proc_name, atm_proc_root); |
379 | kfree(objp: dev->proc_name); |
380 | } |
381 | |
382 | int __init atm_proc_init(void) |
383 | { |
384 | atm_proc_root = proc_net_mkdir(net: &init_net, name: "atm" , parent: init_net.proc_net); |
385 | if (!atm_proc_root) |
386 | return -ENOMEM; |
387 | proc_create_seq("devices" , 0444, atm_proc_root, &atm_dev_seq_ops); |
388 | proc_create_seq_private(name: "pvc" , mode: 0444, parent: atm_proc_root, ops: &pvc_seq_ops, |
389 | state_size: sizeof(struct vcc_state), data: (void *)(uintptr_t)PF_ATMPVC); |
390 | proc_create_seq_private(name: "svc" , mode: 0444, parent: atm_proc_root, ops: &svc_seq_ops, |
391 | state_size: sizeof(struct vcc_state), data: (void *)(uintptr_t)PF_ATMSVC); |
392 | proc_create_seq_private(name: "vc" , mode: 0444, parent: atm_proc_root, ops: &vcc_seq_ops, |
393 | state_size: sizeof(struct vcc_state), NULL); |
394 | return 0; |
395 | } |
396 | |
397 | void atm_proc_exit(void) |
398 | { |
399 | remove_proc_subtree("atm" , init_net.proc_net); |
400 | } |
401 | |