1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* net/atm/resources.c - Statically allocated resources */ |
3 | |
4 | /* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ |
5 | |
6 | /* Fixes |
7 | * Arnaldo Carvalho de Melo <acme@conectiva.com.br> |
8 | * 2002/01 - don't free the whole struct sock on sk->destruct time, |
9 | * use the default destruct function initialized by sock_init_data */ |
10 | |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ |
12 | |
13 | #include <linux/ctype.h> |
14 | #include <linux/string.h> |
15 | #include <linux/atmdev.h> |
16 | #include <linux/sonet.h> |
17 | #include <linux/kernel.h> /* for barrier */ |
18 | #include <linux/module.h> |
19 | #include <linux/bitops.h> |
20 | #include <linux/capability.h> |
21 | #include <linux/delay.h> |
22 | #include <linux/mutex.h> |
23 | #include <linux/slab.h> |
24 | |
25 | #include <net/sock.h> /* for struct sock */ |
26 | |
27 | #include "common.h" |
28 | #include "resources.h" |
29 | #include "addr.h" |
30 | |
31 | |
32 | LIST_HEAD(atm_devs); |
33 | DEFINE_MUTEX(atm_dev_mutex); |
34 | |
35 | static struct atm_dev *__alloc_atm_dev(const char *type) |
36 | { |
37 | struct atm_dev *dev; |
38 | |
39 | dev = kzalloc(size: sizeof(*dev), GFP_KERNEL); |
40 | if (!dev) |
41 | return NULL; |
42 | dev->type = type; |
43 | dev->signal = ATM_PHY_SIG_UNKNOWN; |
44 | dev->link_rate = ATM_OC3_PCR; |
45 | spin_lock_init(&dev->lock); |
46 | INIT_LIST_HEAD(list: &dev->local); |
47 | INIT_LIST_HEAD(list: &dev->lecs); |
48 | |
49 | return dev; |
50 | } |
51 | |
52 | static struct atm_dev *__atm_dev_lookup(int number) |
53 | { |
54 | struct atm_dev *dev; |
55 | |
56 | list_for_each_entry(dev, &atm_devs, dev_list) { |
57 | if (dev->number == number) { |
58 | atm_dev_hold(dev); |
59 | return dev; |
60 | } |
61 | } |
62 | return NULL; |
63 | } |
64 | |
65 | struct atm_dev *atm_dev_lookup(int number) |
66 | { |
67 | struct atm_dev *dev; |
68 | |
69 | mutex_lock(&atm_dev_mutex); |
70 | dev = __atm_dev_lookup(number); |
71 | mutex_unlock(lock: &atm_dev_mutex); |
72 | return dev; |
73 | } |
74 | EXPORT_SYMBOL(atm_dev_lookup); |
75 | |
76 | struct atm_dev *atm_dev_register(const char *type, struct device *parent, |
77 | const struct atmdev_ops *ops, int number, |
78 | unsigned long *flags) |
79 | { |
80 | struct atm_dev *dev, *inuse; |
81 | |
82 | dev = __alloc_atm_dev(type); |
83 | if (!dev) { |
84 | pr_err("no space for dev %s\n" , type); |
85 | return NULL; |
86 | } |
87 | mutex_lock(&atm_dev_mutex); |
88 | if (number != -1) { |
89 | inuse = __atm_dev_lookup(number); |
90 | if (inuse) { |
91 | atm_dev_put(dev: inuse); |
92 | mutex_unlock(lock: &atm_dev_mutex); |
93 | kfree(objp: dev); |
94 | return NULL; |
95 | } |
96 | dev->number = number; |
97 | } else { |
98 | dev->number = 0; |
99 | while ((inuse = __atm_dev_lookup(number: dev->number))) { |
100 | atm_dev_put(dev: inuse); |
101 | dev->number++; |
102 | } |
103 | } |
104 | |
105 | dev->ops = ops; |
106 | if (flags) |
107 | dev->flags = *flags; |
108 | else |
109 | memset(&dev->flags, 0, sizeof(dev->flags)); |
110 | memset(&dev->stats, 0, sizeof(dev->stats)); |
111 | refcount_set(r: &dev->refcnt, n: 1); |
112 | |
113 | if (atm_proc_dev_register(dev) < 0) { |
114 | pr_err("atm_proc_dev_register failed for dev %s\n" , type); |
115 | goto out_fail; |
116 | } |
117 | |
118 | if (atm_register_sysfs(adev: dev, parent) < 0) { |
119 | pr_err("atm_register_sysfs failed for dev %s\n" , type); |
120 | atm_proc_dev_deregister(dev); |
121 | goto out_fail; |
122 | } |
123 | |
124 | list_add_tail(new: &dev->dev_list, head: &atm_devs); |
125 | |
126 | out: |
127 | mutex_unlock(lock: &atm_dev_mutex); |
128 | return dev; |
129 | |
130 | out_fail: |
131 | kfree(objp: dev); |
132 | dev = NULL; |
133 | goto out; |
134 | } |
135 | EXPORT_SYMBOL(atm_dev_register); |
136 | |
137 | void atm_dev_deregister(struct atm_dev *dev) |
138 | { |
139 | BUG_ON(test_bit(ATM_DF_REMOVED, &dev->flags)); |
140 | set_bit(nr: ATM_DF_REMOVED, addr: &dev->flags); |
141 | |
142 | /* |
143 | * if we remove current device from atm_devs list, new device |
144 | * with same number can appear, such we need deregister proc, |
145 | * release async all vccs and remove them from vccs list too |
146 | */ |
147 | mutex_lock(&atm_dev_mutex); |
148 | list_del(entry: &dev->dev_list); |
149 | mutex_unlock(lock: &atm_dev_mutex); |
150 | |
151 | atm_dev_release_vccs(dev); |
152 | atm_unregister_sysfs(adev: dev); |
153 | atm_proc_dev_deregister(dev); |
154 | |
155 | atm_dev_put(dev); |
156 | } |
157 | EXPORT_SYMBOL(atm_dev_deregister); |
158 | |
159 | static void copy_aal_stats(struct k_atm_aal_stats *from, |
160 | struct atm_aal_stats *to) |
161 | { |
162 | #define __HANDLE_ITEM(i) to->i = atomic_read(&from->i) |
163 | __AAL_STAT_ITEMS |
164 | #undef __HANDLE_ITEM |
165 | } |
166 | |
167 | static void subtract_aal_stats(struct k_atm_aal_stats *from, |
168 | struct atm_aal_stats *to) |
169 | { |
170 | #define __HANDLE_ITEM(i) atomic_sub(to->i, &from->i) |
171 | __AAL_STAT_ITEMS |
172 | #undef __HANDLE_ITEM |
173 | } |
174 | |
175 | static int fetch_stats(struct atm_dev *dev, struct atm_dev_stats __user *arg, |
176 | int zero) |
177 | { |
178 | struct atm_dev_stats tmp; |
179 | int error = 0; |
180 | |
181 | copy_aal_stats(from: &dev->stats.aal0, to: &tmp.aal0); |
182 | copy_aal_stats(from: &dev->stats.aal34, to: &tmp.aal34); |
183 | copy_aal_stats(from: &dev->stats.aal5, to: &tmp.aal5); |
184 | if (arg) |
185 | error = copy_to_user(to: arg, from: &tmp, n: sizeof(tmp)); |
186 | if (zero && !error) { |
187 | subtract_aal_stats(from: &dev->stats.aal0, to: &tmp.aal0); |
188 | subtract_aal_stats(from: &dev->stats.aal34, to: &tmp.aal34); |
189 | subtract_aal_stats(from: &dev->stats.aal5, to: &tmp.aal5); |
190 | } |
191 | return error ? -EFAULT : 0; |
192 | } |
193 | |
194 | int atm_getnames(void __user *buf, int __user *iobuf_len) |
195 | { |
196 | int error, len, size = 0; |
197 | struct atm_dev *dev; |
198 | struct list_head *p; |
199 | int *tmp_buf, *tmp_p; |
200 | |
201 | if (get_user(len, iobuf_len)) |
202 | return -EFAULT; |
203 | mutex_lock(&atm_dev_mutex); |
204 | list_for_each(p, &atm_devs) |
205 | size += sizeof(int); |
206 | if (size > len) { |
207 | mutex_unlock(lock: &atm_dev_mutex); |
208 | return -E2BIG; |
209 | } |
210 | tmp_buf = kmalloc(size, GFP_ATOMIC); |
211 | if (!tmp_buf) { |
212 | mutex_unlock(lock: &atm_dev_mutex); |
213 | return -ENOMEM; |
214 | } |
215 | tmp_p = tmp_buf; |
216 | list_for_each_entry(dev, &atm_devs, dev_list) { |
217 | *tmp_p++ = dev->number; |
218 | } |
219 | mutex_unlock(lock: &atm_dev_mutex); |
220 | error = ((copy_to_user(to: buf, from: tmp_buf, n: size)) || |
221 | put_user(size, iobuf_len)) |
222 | ? -EFAULT : 0; |
223 | kfree(objp: tmp_buf); |
224 | return error; |
225 | } |
226 | |
227 | int atm_dev_ioctl(unsigned int cmd, void __user *buf, int __user *sioc_len, |
228 | int number, int compat) |
229 | { |
230 | int error, len, size = 0; |
231 | struct atm_dev *dev; |
232 | |
233 | if (get_user(len, sioc_len)) |
234 | return -EFAULT; |
235 | |
236 | dev = try_then_request_module(atm_dev_lookup(number), "atm-device-%d" , |
237 | number); |
238 | if (!dev) |
239 | return -ENODEV; |
240 | |
241 | switch (cmd) { |
242 | case ATM_GETTYPE: |
243 | size = strlen(dev->type) + 1; |
244 | if (copy_to_user(to: buf, from: dev->type, n: size)) { |
245 | error = -EFAULT; |
246 | goto done; |
247 | } |
248 | break; |
249 | case ATM_GETESI: |
250 | size = ESI_LEN; |
251 | if (copy_to_user(to: buf, from: dev->esi, n: size)) { |
252 | error = -EFAULT; |
253 | goto done; |
254 | } |
255 | break; |
256 | case ATM_SETESI: |
257 | { |
258 | int i; |
259 | |
260 | for (i = 0; i < ESI_LEN; i++) |
261 | if (dev->esi[i]) { |
262 | error = -EEXIST; |
263 | goto done; |
264 | } |
265 | } |
266 | fallthrough; |
267 | case ATM_SETESIF: |
268 | { |
269 | unsigned char esi[ESI_LEN]; |
270 | |
271 | if (!capable(CAP_NET_ADMIN)) { |
272 | error = -EPERM; |
273 | goto done; |
274 | } |
275 | if (copy_from_user(to: esi, from: buf, ESI_LEN)) { |
276 | error = -EFAULT; |
277 | goto done; |
278 | } |
279 | memcpy(dev->esi, esi, ESI_LEN); |
280 | error = ESI_LEN; |
281 | goto done; |
282 | } |
283 | case ATM_GETSTATZ: |
284 | if (!capable(CAP_NET_ADMIN)) { |
285 | error = -EPERM; |
286 | goto done; |
287 | } |
288 | fallthrough; |
289 | case ATM_GETSTAT: |
290 | size = sizeof(struct atm_dev_stats); |
291 | error = fetch_stats(dev, arg: buf, zero: cmd == ATM_GETSTATZ); |
292 | if (error) |
293 | goto done; |
294 | break; |
295 | case ATM_GETCIRANGE: |
296 | size = sizeof(struct atm_cirange); |
297 | if (copy_to_user(to: buf, from: &dev->ci_range, n: size)) { |
298 | error = -EFAULT; |
299 | goto done; |
300 | } |
301 | break; |
302 | case ATM_GETLINKRATE: |
303 | size = sizeof(int); |
304 | if (copy_to_user(to: buf, from: &dev->link_rate, n: size)) { |
305 | error = -EFAULT; |
306 | goto done; |
307 | } |
308 | break; |
309 | case ATM_RSTADDR: |
310 | if (!capable(CAP_NET_ADMIN)) { |
311 | error = -EPERM; |
312 | goto done; |
313 | } |
314 | atm_reset_addr(dev, type: ATM_ADDR_LOCAL); |
315 | break; |
316 | case ATM_ADDADDR: |
317 | case ATM_DELADDR: |
318 | case ATM_ADDLECSADDR: |
319 | case ATM_DELLECSADDR: |
320 | { |
321 | struct sockaddr_atmsvc addr; |
322 | |
323 | if (!capable(CAP_NET_ADMIN)) { |
324 | error = -EPERM; |
325 | goto done; |
326 | } |
327 | |
328 | if (copy_from_user(to: &addr, from: buf, n: sizeof(addr))) { |
329 | error = -EFAULT; |
330 | goto done; |
331 | } |
332 | if (cmd == ATM_ADDADDR || cmd == ATM_ADDLECSADDR) |
333 | error = atm_add_addr(dev, addr: &addr, |
334 | type: (cmd == ATM_ADDADDR ? |
335 | ATM_ADDR_LOCAL : ATM_ADDR_LECS)); |
336 | else |
337 | error = atm_del_addr(dev, addr: &addr, |
338 | type: (cmd == ATM_DELADDR ? |
339 | ATM_ADDR_LOCAL : ATM_ADDR_LECS)); |
340 | goto done; |
341 | } |
342 | case ATM_GETADDR: |
343 | case ATM_GETLECSADDR: |
344 | error = atm_get_addr(dev, buf, size: len, |
345 | type: (cmd == ATM_GETADDR ? |
346 | ATM_ADDR_LOCAL : ATM_ADDR_LECS)); |
347 | if (error < 0) |
348 | goto done; |
349 | size = error; |
350 | /* may return 0, but later on size == 0 means "don't |
351 | write the length" */ |
352 | error = put_user(size, sioc_len) ? -EFAULT : 0; |
353 | goto done; |
354 | case ATM_SETLOOP: |
355 | if (__ATM_LM_XTRMT((int) (unsigned long) buf) && |
356 | __ATM_LM_XTLOC((int) (unsigned long) buf) > |
357 | __ATM_LM_XTRMT((int) (unsigned long) buf)) { |
358 | error = -EINVAL; |
359 | goto done; |
360 | } |
361 | fallthrough; |
362 | case ATM_SETCIRANGE: |
363 | case SONET_GETSTATZ: |
364 | case SONET_SETDIAG: |
365 | case SONET_CLRDIAG: |
366 | case SONET_SETFRAMING: |
367 | if (!capable(CAP_NET_ADMIN)) { |
368 | error = -EPERM; |
369 | goto done; |
370 | } |
371 | fallthrough; |
372 | default: |
373 | if (IS_ENABLED(CONFIG_COMPAT) && compat) { |
374 | #ifdef CONFIG_COMPAT |
375 | if (!dev->ops->compat_ioctl) { |
376 | error = -EINVAL; |
377 | goto done; |
378 | } |
379 | size = dev->ops->compat_ioctl(dev, cmd, buf); |
380 | #endif |
381 | } else { |
382 | if (!dev->ops->ioctl) { |
383 | error = -EINVAL; |
384 | goto done; |
385 | } |
386 | size = dev->ops->ioctl(dev, cmd, buf); |
387 | } |
388 | if (size < 0) { |
389 | error = (size == -ENOIOCTLCMD ? -ENOTTY : size); |
390 | goto done; |
391 | } |
392 | } |
393 | |
394 | if (size) |
395 | error = put_user(size, sioc_len) ? -EFAULT : 0; |
396 | else |
397 | error = 0; |
398 | done: |
399 | atm_dev_put(dev); |
400 | return error; |
401 | } |
402 | |
403 | #ifdef CONFIG_PROC_FS |
404 | void *atm_dev_seq_start(struct seq_file *seq, loff_t *pos) |
405 | { |
406 | mutex_lock(&atm_dev_mutex); |
407 | return seq_list_start_head(head: &atm_devs, pos: *pos); |
408 | } |
409 | |
410 | void atm_dev_seq_stop(struct seq_file *seq, void *v) |
411 | { |
412 | mutex_unlock(lock: &atm_dev_mutex); |
413 | } |
414 | |
415 | void *atm_dev_seq_next(struct seq_file *seq, void *v, loff_t *pos) |
416 | { |
417 | return seq_list_next(v, head: &atm_devs, ppos: pos); |
418 | } |
419 | #endif |
420 | |