diff options
Diffstat (limited to 'net/core/ethtool.c')
-rw-r--r-- | net/core/ethtool.c | 819 |
1 files changed, 819 insertions, 0 deletions
diff --git a/net/core/ethtool.c b/net/core/ethtool.c new file mode 100644 index 000000000000..f05fde97c43d --- /dev/null +++ b/net/core/ethtool.c | |||
@@ -0,0 +1,819 @@ | |||
1 | /* | ||
2 | * net/core/ethtool.c - Ethtool ioctl handler | ||
3 | * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx> | ||
4 | * | ||
5 | * This file is where we call all the ethtool_ops commands to get | ||
6 | * the information ethtool needs. We fall back to calling do_ioctl() | ||
7 | * for drivers which haven't been converted to ethtool_ops yet. | ||
8 | * | ||
9 | * It's GPL, stupid. | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/types.h> | ||
14 | #include <linux/errno.h> | ||
15 | #include <linux/ethtool.h> | ||
16 | #include <linux/netdevice.h> | ||
17 | #include <asm/uaccess.h> | ||
18 | |||
19 | /* | ||
20 | * Some useful ethtool_ops methods that're device independent. | ||
21 | * If we find that all drivers want to do the same thing here, | ||
22 | * we can turn these into dev_() function calls. | ||
23 | */ | ||
24 | |||
25 | u32 ethtool_op_get_link(struct net_device *dev) | ||
26 | { | ||
27 | return netif_carrier_ok(dev) ? 1 : 0; | ||
28 | } | ||
29 | |||
30 | u32 ethtool_op_get_tx_csum(struct net_device *dev) | ||
31 | { | ||
32 | return (dev->features & NETIF_F_IP_CSUM) != 0; | ||
33 | } | ||
34 | |||
35 | int ethtool_op_set_tx_csum(struct net_device *dev, u32 data) | ||
36 | { | ||
37 | if (data) | ||
38 | dev->features |= NETIF_F_IP_CSUM; | ||
39 | else | ||
40 | dev->features &= ~NETIF_F_IP_CSUM; | ||
41 | |||
42 | return 0; | ||
43 | } | ||
44 | |||
45 | u32 ethtool_op_get_sg(struct net_device *dev) | ||
46 | { | ||
47 | return (dev->features & NETIF_F_SG) != 0; | ||
48 | } | ||
49 | |||
50 | int ethtool_op_set_sg(struct net_device *dev, u32 data) | ||
51 | { | ||
52 | if (data) | ||
53 | dev->features |= NETIF_F_SG; | ||
54 | else | ||
55 | dev->features &= ~NETIF_F_SG; | ||
56 | |||
57 | return 0; | ||
58 | } | ||
59 | |||
60 | u32 ethtool_op_get_tso(struct net_device *dev) | ||
61 | { | ||
62 | return (dev->features & NETIF_F_TSO) != 0; | ||
63 | } | ||
64 | |||
65 | int ethtool_op_set_tso(struct net_device *dev, u32 data) | ||
66 | { | ||
67 | if (data) | ||
68 | dev->features |= NETIF_F_TSO; | ||
69 | else | ||
70 | dev->features &= ~NETIF_F_TSO; | ||
71 | |||
72 | return 0; | ||
73 | } | ||
74 | |||
75 | /* Handlers for each ethtool command */ | ||
76 | |||
77 | static int ethtool_get_settings(struct net_device *dev, void __user *useraddr) | ||
78 | { | ||
79 | struct ethtool_cmd cmd = { ETHTOOL_GSET }; | ||
80 | int err; | ||
81 | |||
82 | if (!dev->ethtool_ops->get_settings) | ||
83 | return -EOPNOTSUPP; | ||
84 | |||
85 | err = dev->ethtool_ops->get_settings(dev, &cmd); | ||
86 | if (err < 0) | ||
87 | return err; | ||
88 | |||
89 | if (copy_to_user(useraddr, &cmd, sizeof(cmd))) | ||
90 | return -EFAULT; | ||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | static int ethtool_set_settings(struct net_device *dev, void __user *useraddr) | ||
95 | { | ||
96 | struct ethtool_cmd cmd; | ||
97 | |||
98 | if (!dev->ethtool_ops->set_settings) | ||
99 | return -EOPNOTSUPP; | ||
100 | |||
101 | if (copy_from_user(&cmd, useraddr, sizeof(cmd))) | ||
102 | return -EFAULT; | ||
103 | |||
104 | return dev->ethtool_ops->set_settings(dev, &cmd); | ||
105 | } | ||
106 | |||
107 | static int ethtool_get_drvinfo(struct net_device *dev, void __user *useraddr) | ||
108 | { | ||
109 | struct ethtool_drvinfo info; | ||
110 | struct ethtool_ops *ops = dev->ethtool_ops; | ||
111 | |||
112 | if (!ops->get_drvinfo) | ||
113 | return -EOPNOTSUPP; | ||
114 | |||
115 | memset(&info, 0, sizeof(info)); | ||
116 | info.cmd = ETHTOOL_GDRVINFO; | ||
117 | ops->get_drvinfo(dev, &info); | ||
118 | |||
119 | if (ops->self_test_count) | ||
120 | info.testinfo_len = ops->self_test_count(dev); | ||
121 | if (ops->get_stats_count) | ||
122 | info.n_stats = ops->get_stats_count(dev); | ||
123 | if (ops->get_regs_len) | ||
124 | info.regdump_len = ops->get_regs_len(dev); | ||
125 | if (ops->get_eeprom_len) | ||
126 | info.eedump_len = ops->get_eeprom_len(dev); | ||
127 | |||
128 | if (copy_to_user(useraddr, &info, sizeof(info))) | ||
129 | return -EFAULT; | ||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | static int ethtool_get_regs(struct net_device *dev, char __user *useraddr) | ||
134 | { | ||
135 | struct ethtool_regs regs; | ||
136 | struct ethtool_ops *ops = dev->ethtool_ops; | ||
137 | void *regbuf; | ||
138 | int reglen, ret; | ||
139 | |||
140 | if (!ops->get_regs || !ops->get_regs_len) | ||
141 | return -EOPNOTSUPP; | ||
142 | |||
143 | if (copy_from_user(®s, useraddr, sizeof(regs))) | ||
144 | return -EFAULT; | ||
145 | |||
146 | reglen = ops->get_regs_len(dev); | ||
147 | if (regs.len > reglen) | ||
148 | regs.len = reglen; | ||
149 | |||
150 | regbuf = kmalloc(reglen, GFP_USER); | ||
151 | if (!regbuf) | ||
152 | return -ENOMEM; | ||
153 | |||
154 | ops->get_regs(dev, ®s, regbuf); | ||
155 | |||
156 | ret = -EFAULT; | ||
157 | if (copy_to_user(useraddr, ®s, sizeof(regs))) | ||
158 | goto out; | ||
159 | useraddr += offsetof(struct ethtool_regs, data); | ||
160 | if (copy_to_user(useraddr, regbuf, regs.len)) | ||
161 | goto out; | ||
162 | ret = 0; | ||
163 | |||
164 | out: | ||
165 | kfree(regbuf); | ||
166 | return ret; | ||
167 | } | ||
168 | |||
169 | static int ethtool_get_wol(struct net_device *dev, char __user *useraddr) | ||
170 | { | ||
171 | struct ethtool_wolinfo wol = { ETHTOOL_GWOL }; | ||
172 | |||
173 | if (!dev->ethtool_ops->get_wol) | ||
174 | return -EOPNOTSUPP; | ||
175 | |||
176 | dev->ethtool_ops->get_wol(dev, &wol); | ||
177 | |||
178 | if (copy_to_user(useraddr, &wol, sizeof(wol))) | ||
179 | return -EFAULT; | ||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | static int ethtool_set_wol(struct net_device *dev, char __user *useraddr) | ||
184 | { | ||
185 | struct ethtool_wolinfo wol; | ||
186 | |||
187 | if (!dev->ethtool_ops->set_wol) | ||
188 | return -EOPNOTSUPP; | ||
189 | |||
190 | if (copy_from_user(&wol, useraddr, sizeof(wol))) | ||
191 | return -EFAULT; | ||
192 | |||
193 | return dev->ethtool_ops->set_wol(dev, &wol); | ||
194 | } | ||
195 | |||
196 | static int ethtool_get_msglevel(struct net_device *dev, char __user *useraddr) | ||
197 | { | ||
198 | struct ethtool_value edata = { ETHTOOL_GMSGLVL }; | ||
199 | |||
200 | if (!dev->ethtool_ops->get_msglevel) | ||
201 | return -EOPNOTSUPP; | ||
202 | |||
203 | edata.data = dev->ethtool_ops->get_msglevel(dev); | ||
204 | |||
205 | if (copy_to_user(useraddr, &edata, sizeof(edata))) | ||
206 | return -EFAULT; | ||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | static int ethtool_set_msglevel(struct net_device *dev, char __user *useraddr) | ||
211 | { | ||
212 | struct ethtool_value edata; | ||
213 | |||
214 | if (!dev->ethtool_ops->set_msglevel) | ||
215 | return -EOPNOTSUPP; | ||
216 | |||
217 | if (copy_from_user(&edata, useraddr, sizeof(edata))) | ||
218 | return -EFAULT; | ||
219 | |||
220 | dev->ethtool_ops->set_msglevel(dev, edata.data); | ||
221 | return 0; | ||
222 | } | ||
223 | |||
224 | static int ethtool_nway_reset(struct net_device *dev) | ||
225 | { | ||
226 | if (!dev->ethtool_ops->nway_reset) | ||
227 | return -EOPNOTSUPP; | ||
228 | |||
229 | return dev->ethtool_ops->nway_reset(dev); | ||
230 | } | ||
231 | |||
232 | static int ethtool_get_link(struct net_device *dev, void __user *useraddr) | ||
233 | { | ||
234 | struct ethtool_value edata = { ETHTOOL_GLINK }; | ||
235 | |||
236 | if (!dev->ethtool_ops->get_link) | ||
237 | return -EOPNOTSUPP; | ||
238 | |||
239 | edata.data = dev->ethtool_ops->get_link(dev); | ||
240 | |||
241 | if (copy_to_user(useraddr, &edata, sizeof(edata))) | ||
242 | return -EFAULT; | ||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr) | ||
247 | { | ||
248 | struct ethtool_eeprom eeprom; | ||
249 | struct ethtool_ops *ops = dev->ethtool_ops; | ||
250 | u8 *data; | ||
251 | int ret; | ||
252 | |||
253 | if (!ops->get_eeprom || !ops->get_eeprom_len) | ||
254 | return -EOPNOTSUPP; | ||
255 | |||
256 | if (copy_from_user(&eeprom, useraddr, sizeof(eeprom))) | ||
257 | return -EFAULT; | ||
258 | |||
259 | /* Check for wrap and zero */ | ||
260 | if (eeprom.offset + eeprom.len <= eeprom.offset) | ||
261 | return -EINVAL; | ||
262 | |||
263 | /* Check for exceeding total eeprom len */ | ||
264 | if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev)) | ||
265 | return -EINVAL; | ||
266 | |||
267 | data = kmalloc(eeprom.len, GFP_USER); | ||
268 | if (!data) | ||
269 | return -ENOMEM; | ||
270 | |||
271 | ret = -EFAULT; | ||
272 | if (copy_from_user(data, useraddr + sizeof(eeprom), eeprom.len)) | ||
273 | goto out; | ||
274 | |||
275 | ret = ops->get_eeprom(dev, &eeprom, data); | ||
276 | if (ret) | ||
277 | goto out; | ||
278 | |||
279 | ret = -EFAULT; | ||
280 | if (copy_to_user(useraddr, &eeprom, sizeof(eeprom))) | ||
281 | goto out; | ||
282 | if (copy_to_user(useraddr + sizeof(eeprom), data, eeprom.len)) | ||
283 | goto out; | ||
284 | ret = 0; | ||
285 | |||
286 | out: | ||
287 | kfree(data); | ||
288 | return ret; | ||
289 | } | ||
290 | |||
291 | static int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr) | ||
292 | { | ||
293 | struct ethtool_eeprom eeprom; | ||
294 | struct ethtool_ops *ops = dev->ethtool_ops; | ||
295 | u8 *data; | ||
296 | int ret; | ||
297 | |||
298 | if (!ops->set_eeprom || !ops->get_eeprom_len) | ||
299 | return -EOPNOTSUPP; | ||
300 | |||
301 | if (copy_from_user(&eeprom, useraddr, sizeof(eeprom))) | ||
302 | return -EFAULT; | ||
303 | |||
304 | /* Check for wrap and zero */ | ||
305 | if (eeprom.offset + eeprom.len <= eeprom.offset) | ||
306 | return -EINVAL; | ||
307 | |||
308 | /* Check for exceeding total eeprom len */ | ||
309 | if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev)) | ||
310 | return -EINVAL; | ||
311 | |||
312 | data = kmalloc(eeprom.len, GFP_USER); | ||
313 | if (!data) | ||
314 | return -ENOMEM; | ||
315 | |||
316 | ret = -EFAULT; | ||
317 | if (copy_from_user(data, useraddr + sizeof(eeprom), eeprom.len)) | ||
318 | goto out; | ||
319 | |||
320 | ret = ops->set_eeprom(dev, &eeprom, data); | ||
321 | if (ret) | ||
322 | goto out; | ||
323 | |||
324 | if (copy_to_user(useraddr + sizeof(eeprom), data, eeprom.len)) | ||
325 | ret = -EFAULT; | ||
326 | |||
327 | out: | ||
328 | kfree(data); | ||
329 | return ret; | ||
330 | } | ||
331 | |||
332 | static int ethtool_get_coalesce(struct net_device *dev, void __user *useraddr) | ||
333 | { | ||
334 | struct ethtool_coalesce coalesce = { ETHTOOL_GCOALESCE }; | ||
335 | |||
336 | if (!dev->ethtool_ops->get_coalesce) | ||
337 | return -EOPNOTSUPP; | ||
338 | |||
339 | dev->ethtool_ops->get_coalesce(dev, &coalesce); | ||
340 | |||
341 | if (copy_to_user(useraddr, &coalesce, sizeof(coalesce))) | ||
342 | return -EFAULT; | ||
343 | return 0; | ||
344 | } | ||
345 | |||
346 | static int ethtool_set_coalesce(struct net_device *dev, void __user *useraddr) | ||
347 | { | ||
348 | struct ethtool_coalesce coalesce; | ||
349 | |||
350 | if (!dev->ethtool_ops->get_coalesce) | ||
351 | return -EOPNOTSUPP; | ||
352 | |||
353 | if (copy_from_user(&coalesce, useraddr, sizeof(coalesce))) | ||
354 | return -EFAULT; | ||
355 | |||
356 | return dev->ethtool_ops->set_coalesce(dev, &coalesce); | ||
357 | } | ||
358 | |||
359 | static int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr) | ||
360 | { | ||
361 | struct ethtool_ringparam ringparam = { ETHTOOL_GRINGPARAM }; | ||
362 | |||
363 | if (!dev->ethtool_ops->get_ringparam) | ||
364 | return -EOPNOTSUPP; | ||
365 | |||
366 | dev->ethtool_ops->get_ringparam(dev, &ringparam); | ||
367 | |||
368 | if (copy_to_user(useraddr, &ringparam, sizeof(ringparam))) | ||
369 | return -EFAULT; | ||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr) | ||
374 | { | ||
375 | struct ethtool_ringparam ringparam; | ||
376 | |||
377 | if (!dev->ethtool_ops->set_ringparam) | ||
378 | return -EOPNOTSUPP; | ||
379 | |||
380 | if (copy_from_user(&ringparam, useraddr, sizeof(ringparam))) | ||
381 | return -EFAULT; | ||
382 | |||
383 | return dev->ethtool_ops->set_ringparam(dev, &ringparam); | ||
384 | } | ||
385 | |||
386 | static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr) | ||
387 | { | ||
388 | struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM }; | ||
389 | |||
390 | if (!dev->ethtool_ops->get_pauseparam) | ||
391 | return -EOPNOTSUPP; | ||
392 | |||
393 | dev->ethtool_ops->get_pauseparam(dev, &pauseparam); | ||
394 | |||
395 | if (copy_to_user(useraddr, &pauseparam, sizeof(pauseparam))) | ||
396 | return -EFAULT; | ||
397 | return 0; | ||
398 | } | ||
399 | |||
400 | static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr) | ||
401 | { | ||
402 | struct ethtool_pauseparam pauseparam; | ||
403 | |||
404 | if (!dev->ethtool_ops->get_pauseparam) | ||
405 | return -EOPNOTSUPP; | ||
406 | |||
407 | if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam))) | ||
408 | return -EFAULT; | ||
409 | |||
410 | return dev->ethtool_ops->set_pauseparam(dev, &pauseparam); | ||
411 | } | ||
412 | |||
413 | static int ethtool_get_rx_csum(struct net_device *dev, char __user *useraddr) | ||
414 | { | ||
415 | struct ethtool_value edata = { ETHTOOL_GRXCSUM }; | ||
416 | |||
417 | if (!dev->ethtool_ops->get_rx_csum) | ||
418 | return -EOPNOTSUPP; | ||
419 | |||
420 | edata.data = dev->ethtool_ops->get_rx_csum(dev); | ||
421 | |||
422 | if (copy_to_user(useraddr, &edata, sizeof(edata))) | ||
423 | return -EFAULT; | ||
424 | return 0; | ||
425 | } | ||
426 | |||
427 | static int ethtool_set_rx_csum(struct net_device *dev, char __user *useraddr) | ||
428 | { | ||
429 | struct ethtool_value edata; | ||
430 | |||
431 | if (!dev->ethtool_ops->set_rx_csum) | ||
432 | return -EOPNOTSUPP; | ||
433 | |||
434 | if (copy_from_user(&edata, useraddr, sizeof(edata))) | ||
435 | return -EFAULT; | ||
436 | |||
437 | dev->ethtool_ops->set_rx_csum(dev, edata.data); | ||
438 | return 0; | ||
439 | } | ||
440 | |||
441 | static int ethtool_get_tx_csum(struct net_device *dev, char __user *useraddr) | ||
442 | { | ||
443 | struct ethtool_value edata = { ETHTOOL_GTXCSUM }; | ||
444 | |||
445 | if (!dev->ethtool_ops->get_tx_csum) | ||
446 | return -EOPNOTSUPP; | ||
447 | |||
448 | edata.data = dev->ethtool_ops->get_tx_csum(dev); | ||
449 | |||
450 | if (copy_to_user(useraddr, &edata, sizeof(edata))) | ||
451 | return -EFAULT; | ||
452 | return 0; | ||
453 | } | ||
454 | |||
455 | static int __ethtool_set_sg(struct net_device *dev, u32 data) | ||
456 | { | ||
457 | int err; | ||
458 | |||
459 | if (!data && dev->ethtool_ops->set_tso) { | ||
460 | err = dev->ethtool_ops->set_tso(dev, 0); | ||
461 | if (err) | ||
462 | return err; | ||
463 | } | ||
464 | |||
465 | return dev->ethtool_ops->set_sg(dev, data); | ||
466 | } | ||
467 | |||
468 | static int ethtool_set_tx_csum(struct net_device *dev, char __user *useraddr) | ||
469 | { | ||
470 | struct ethtool_value edata; | ||
471 | int err; | ||
472 | |||
473 | if (!dev->ethtool_ops->set_tx_csum) | ||
474 | return -EOPNOTSUPP; | ||
475 | |||
476 | if (copy_from_user(&edata, useraddr, sizeof(edata))) | ||
477 | return -EFAULT; | ||
478 | |||
479 | if (!edata.data && dev->ethtool_ops->set_sg) { | ||
480 | err = __ethtool_set_sg(dev, 0); | ||
481 | if (err) | ||
482 | return err; | ||
483 | } | ||
484 | |||
485 | return dev->ethtool_ops->set_tx_csum(dev, edata.data); | ||
486 | } | ||
487 | |||
488 | static int ethtool_get_sg(struct net_device *dev, char __user *useraddr) | ||
489 | { | ||
490 | struct ethtool_value edata = { ETHTOOL_GSG }; | ||
491 | |||
492 | if (!dev->ethtool_ops->get_sg) | ||
493 | return -EOPNOTSUPP; | ||
494 | |||
495 | edata.data = dev->ethtool_ops->get_sg(dev); | ||
496 | |||
497 | if (copy_to_user(useraddr, &edata, sizeof(edata))) | ||
498 | return -EFAULT; | ||
499 | return 0; | ||
500 | } | ||
501 | |||
502 | static int ethtool_set_sg(struct net_device *dev, char __user *useraddr) | ||
503 | { | ||
504 | struct ethtool_value edata; | ||
505 | |||
506 | if (!dev->ethtool_ops->set_sg) | ||
507 | return -EOPNOTSUPP; | ||
508 | |||
509 | if (copy_from_user(&edata, useraddr, sizeof(edata))) | ||
510 | return -EFAULT; | ||
511 | |||
512 | if (edata.data && | ||
513 | !(dev->features & (NETIF_F_IP_CSUM | | ||
514 | NETIF_F_NO_CSUM | | ||
515 | NETIF_F_HW_CSUM))) | ||
516 | return -EINVAL; | ||
517 | |||
518 | return __ethtool_set_sg(dev, edata.data); | ||
519 | } | ||
520 | |||
521 | static int ethtool_get_tso(struct net_device *dev, char __user *useraddr) | ||
522 | { | ||
523 | struct ethtool_value edata = { ETHTOOL_GTSO }; | ||
524 | |||
525 | if (!dev->ethtool_ops->get_tso) | ||
526 | return -EOPNOTSUPP; | ||
527 | |||
528 | edata.data = dev->ethtool_ops->get_tso(dev); | ||
529 | |||
530 | if (copy_to_user(useraddr, &edata, sizeof(edata))) | ||
531 | return -EFAULT; | ||
532 | return 0; | ||
533 | } | ||
534 | |||
535 | static int ethtool_set_tso(struct net_device *dev, char __user *useraddr) | ||
536 | { | ||
537 | struct ethtool_value edata; | ||
538 | |||
539 | if (!dev->ethtool_ops->set_tso) | ||
540 | return -EOPNOTSUPP; | ||
541 | |||
542 | if (copy_from_user(&edata, useraddr, sizeof(edata))) | ||
543 | return -EFAULT; | ||
544 | |||
545 | if (edata.data && !(dev->features & NETIF_F_SG)) | ||
546 | return -EINVAL; | ||
547 | |||
548 | return dev->ethtool_ops->set_tso(dev, edata.data); | ||
549 | } | ||
550 | |||
551 | static int ethtool_self_test(struct net_device *dev, char __user *useraddr) | ||
552 | { | ||
553 | struct ethtool_test test; | ||
554 | struct ethtool_ops *ops = dev->ethtool_ops; | ||
555 | u64 *data; | ||
556 | int ret; | ||
557 | |||
558 | if (!ops->self_test || !ops->self_test_count) | ||
559 | return -EOPNOTSUPP; | ||
560 | |||
561 | if (copy_from_user(&test, useraddr, sizeof(test))) | ||
562 | return -EFAULT; | ||
563 | |||
564 | test.len = ops->self_test_count(dev); | ||
565 | data = kmalloc(test.len * sizeof(u64), GFP_USER); | ||
566 | if (!data) | ||
567 | return -ENOMEM; | ||
568 | |||
569 | ops->self_test(dev, &test, data); | ||
570 | |||
571 | ret = -EFAULT; | ||
572 | if (copy_to_user(useraddr, &test, sizeof(test))) | ||
573 | goto out; | ||
574 | useraddr += sizeof(test); | ||
575 | if (copy_to_user(useraddr, data, test.len * sizeof(u64))) | ||
576 | goto out; | ||
577 | ret = 0; | ||
578 | |||
579 | out: | ||
580 | kfree(data); | ||
581 | return ret; | ||
582 | } | ||
583 | |||
584 | static int ethtool_get_strings(struct net_device *dev, void __user *useraddr) | ||
585 | { | ||
586 | struct ethtool_gstrings gstrings; | ||
587 | struct ethtool_ops *ops = dev->ethtool_ops; | ||
588 | u8 *data; | ||
589 | int ret; | ||
590 | |||
591 | if (!ops->get_strings) | ||
592 | return -EOPNOTSUPP; | ||
593 | |||
594 | if (copy_from_user(&gstrings, useraddr, sizeof(gstrings))) | ||
595 | return -EFAULT; | ||
596 | |||
597 | switch (gstrings.string_set) { | ||
598 | case ETH_SS_TEST: | ||
599 | if (!ops->self_test_count) | ||
600 | return -EOPNOTSUPP; | ||
601 | gstrings.len = ops->self_test_count(dev); | ||
602 | break; | ||
603 | case ETH_SS_STATS: | ||
604 | if (!ops->get_stats_count) | ||
605 | return -EOPNOTSUPP; | ||
606 | gstrings.len = ops->get_stats_count(dev); | ||
607 | break; | ||
608 | default: | ||
609 | return -EINVAL; | ||
610 | } | ||
611 | |||
612 | data = kmalloc(gstrings.len * ETH_GSTRING_LEN, GFP_USER); | ||
613 | if (!data) | ||
614 | return -ENOMEM; | ||
615 | |||
616 | ops->get_strings(dev, gstrings.string_set, data); | ||
617 | |||
618 | ret = -EFAULT; | ||
619 | if (copy_to_user(useraddr, &gstrings, sizeof(gstrings))) | ||
620 | goto out; | ||
621 | useraddr += sizeof(gstrings); | ||
622 | if (copy_to_user(useraddr, data, gstrings.len * ETH_GSTRING_LEN)) | ||
623 | goto out; | ||
624 | ret = 0; | ||
625 | |||
626 | out: | ||
627 | kfree(data); | ||
628 | return ret; | ||
629 | } | ||
630 | |||
631 | static int ethtool_phys_id(struct net_device *dev, void __user *useraddr) | ||
632 | { | ||
633 | struct ethtool_value id; | ||
634 | |||
635 | if (!dev->ethtool_ops->phys_id) | ||
636 | return -EOPNOTSUPP; | ||
637 | |||
638 | if (copy_from_user(&id, useraddr, sizeof(id))) | ||
639 | return -EFAULT; | ||
640 | |||
641 | return dev->ethtool_ops->phys_id(dev, id.data); | ||
642 | } | ||
643 | |||
644 | static int ethtool_get_stats(struct net_device *dev, void __user *useraddr) | ||
645 | { | ||
646 | struct ethtool_stats stats; | ||
647 | struct ethtool_ops *ops = dev->ethtool_ops; | ||
648 | u64 *data; | ||
649 | int ret; | ||
650 | |||
651 | if (!ops->get_ethtool_stats || !ops->get_stats_count) | ||
652 | return -EOPNOTSUPP; | ||
653 | |||
654 | if (copy_from_user(&stats, useraddr, sizeof(stats))) | ||
655 | return -EFAULT; | ||
656 | |||
657 | stats.n_stats = ops->get_stats_count(dev); | ||
658 | data = kmalloc(stats.n_stats * sizeof(u64), GFP_USER); | ||
659 | if (!data) | ||
660 | return -ENOMEM; | ||
661 | |||
662 | ops->get_ethtool_stats(dev, &stats, data); | ||
663 | |||
664 | ret = -EFAULT; | ||
665 | if (copy_to_user(useraddr, &stats, sizeof(stats))) | ||
666 | goto out; | ||
667 | useraddr += sizeof(stats); | ||
668 | if (copy_to_user(useraddr, data, stats.n_stats * sizeof(u64))) | ||
669 | goto out; | ||
670 | ret = 0; | ||
671 | |||
672 | out: | ||
673 | kfree(data); | ||
674 | return ret; | ||
675 | } | ||
676 | |||
677 | /* The main entry point in this file. Called from net/core/dev.c */ | ||
678 | |||
679 | int dev_ethtool(struct ifreq *ifr) | ||
680 | { | ||
681 | struct net_device *dev = __dev_get_by_name(ifr->ifr_name); | ||
682 | void __user *useraddr = ifr->ifr_data; | ||
683 | u32 ethcmd; | ||
684 | int rc; | ||
685 | |||
686 | /* | ||
687 | * XXX: This can be pushed down into the ethtool_* handlers that | ||
688 | * need it. Keep existing behaviour for the moment. | ||
689 | */ | ||
690 | if (!capable(CAP_NET_ADMIN)) | ||
691 | return -EPERM; | ||
692 | |||
693 | if (!dev || !netif_device_present(dev)) | ||
694 | return -ENODEV; | ||
695 | |||
696 | if (!dev->ethtool_ops) | ||
697 | goto ioctl; | ||
698 | |||
699 | if (copy_from_user(ðcmd, useraddr, sizeof (ethcmd))) | ||
700 | return -EFAULT; | ||
701 | |||
702 | if(dev->ethtool_ops->begin) | ||
703 | if ((rc = dev->ethtool_ops->begin(dev)) < 0) | ||
704 | return rc; | ||
705 | |||
706 | switch (ethcmd) { | ||
707 | case ETHTOOL_GSET: | ||
708 | rc = ethtool_get_settings(dev, useraddr); | ||
709 | break; | ||
710 | case ETHTOOL_SSET: | ||
711 | rc = ethtool_set_settings(dev, useraddr); | ||
712 | break; | ||
713 | case ETHTOOL_GDRVINFO: | ||
714 | rc = ethtool_get_drvinfo(dev, useraddr); | ||
715 | |||
716 | break; | ||
717 | case ETHTOOL_GREGS: | ||
718 | rc = ethtool_get_regs(dev, useraddr); | ||
719 | break; | ||
720 | case ETHTOOL_GWOL: | ||
721 | rc = ethtool_get_wol(dev, useraddr); | ||
722 | break; | ||
723 | case ETHTOOL_SWOL: | ||
724 | rc = ethtool_set_wol(dev, useraddr); | ||
725 | break; | ||
726 | case ETHTOOL_GMSGLVL: | ||
727 | rc = ethtool_get_msglevel(dev, useraddr); | ||
728 | break; | ||
729 | case ETHTOOL_SMSGLVL: | ||
730 | rc = ethtool_set_msglevel(dev, useraddr); | ||
731 | break; | ||
732 | case ETHTOOL_NWAY_RST: | ||
733 | rc = ethtool_nway_reset(dev); | ||
734 | break; | ||
735 | case ETHTOOL_GLINK: | ||
736 | rc = ethtool_get_link(dev, useraddr); | ||
737 | break; | ||
738 | case ETHTOOL_GEEPROM: | ||
739 | rc = ethtool_get_eeprom(dev, useraddr); | ||
740 | break; | ||
741 | case ETHTOOL_SEEPROM: | ||
742 | rc = ethtool_set_eeprom(dev, useraddr); | ||
743 | break; | ||
744 | case ETHTOOL_GCOALESCE: | ||
745 | rc = ethtool_get_coalesce(dev, useraddr); | ||
746 | break; | ||
747 | case ETHTOOL_SCOALESCE: | ||
748 | rc = ethtool_set_coalesce(dev, useraddr); | ||
749 | break; | ||
750 | case ETHTOOL_GRINGPARAM: | ||
751 | rc = ethtool_get_ringparam(dev, useraddr); | ||
752 | break; | ||
753 | case ETHTOOL_SRINGPARAM: | ||
754 | rc = ethtool_set_ringparam(dev, useraddr); | ||
755 | break; | ||
756 | case ETHTOOL_GPAUSEPARAM: | ||
757 | rc = ethtool_get_pauseparam(dev, useraddr); | ||
758 | break; | ||
759 | case ETHTOOL_SPAUSEPARAM: | ||
760 | rc = ethtool_set_pauseparam(dev, useraddr); | ||
761 | break; | ||
762 | case ETHTOOL_GRXCSUM: | ||
763 | rc = ethtool_get_rx_csum(dev, useraddr); | ||
764 | break; | ||
765 | case ETHTOOL_SRXCSUM: | ||
766 | rc = ethtool_set_rx_csum(dev, useraddr); | ||
767 | break; | ||
768 | case ETHTOOL_GTXCSUM: | ||
769 | rc = ethtool_get_tx_csum(dev, useraddr); | ||
770 | break; | ||
771 | case ETHTOOL_STXCSUM: | ||
772 | rc = ethtool_set_tx_csum(dev, useraddr); | ||
773 | break; | ||
774 | case ETHTOOL_GSG: | ||
775 | rc = ethtool_get_sg(dev, useraddr); | ||
776 | break; | ||
777 | case ETHTOOL_SSG: | ||
778 | rc = ethtool_set_sg(dev, useraddr); | ||
779 | break; | ||
780 | case ETHTOOL_GTSO: | ||
781 | rc = ethtool_get_tso(dev, useraddr); | ||
782 | break; | ||
783 | case ETHTOOL_STSO: | ||
784 | rc = ethtool_set_tso(dev, useraddr); | ||
785 | break; | ||
786 | case ETHTOOL_TEST: | ||
787 | rc = ethtool_self_test(dev, useraddr); | ||
788 | break; | ||
789 | case ETHTOOL_GSTRINGS: | ||
790 | rc = ethtool_get_strings(dev, useraddr); | ||
791 | break; | ||
792 | case ETHTOOL_PHYS_ID: | ||
793 | rc = ethtool_phys_id(dev, useraddr); | ||
794 | break; | ||
795 | case ETHTOOL_GSTATS: | ||
796 | rc = ethtool_get_stats(dev, useraddr); | ||
797 | break; | ||
798 | default: | ||
799 | rc = -EOPNOTSUPP; | ||
800 | } | ||
801 | |||
802 | if(dev->ethtool_ops->complete) | ||
803 | dev->ethtool_ops->complete(dev); | ||
804 | return rc; | ||
805 | |||
806 | ioctl: | ||
807 | if (dev->do_ioctl) | ||
808 | return dev->do_ioctl(dev, ifr, SIOCETHTOOL); | ||
809 | return -EOPNOTSUPP; | ||
810 | } | ||
811 | |||
812 | EXPORT_SYMBOL(dev_ethtool); | ||
813 | EXPORT_SYMBOL(ethtool_op_get_link); | ||
814 | EXPORT_SYMBOL(ethtool_op_get_sg); | ||
815 | EXPORT_SYMBOL(ethtool_op_get_tso); | ||
816 | EXPORT_SYMBOL(ethtool_op_get_tx_csum); | ||
817 | EXPORT_SYMBOL(ethtool_op_set_sg); | ||
818 | EXPORT_SYMBOL(ethtool_op_set_tso); | ||
819 | EXPORT_SYMBOL(ethtool_op_set_tx_csum); | ||