diff options
Diffstat (limited to 'net/core/ethtool.c')
| -rw-r--r-- | net/core/ethtool.c | 221 |
1 files changed, 210 insertions, 11 deletions
diff --git a/net/core/ethtool.c b/net/core/ethtool.c index 74ead9eca126..fd14116ad7f0 100644 --- a/net/core/ethtool.c +++ b/net/core/ethtool.c | |||
| @@ -21,6 +21,8 @@ | |||
| 21 | #include <linux/uaccess.h> | 21 | #include <linux/uaccess.h> |
| 22 | #include <linux/vmalloc.h> | 22 | #include <linux/vmalloc.h> |
| 23 | #include <linux/slab.h> | 23 | #include <linux/slab.h> |
| 24 | #include <linux/rtnetlink.h> | ||
| 25 | #include <linux/sched.h> | ||
| 24 | 26 | ||
| 25 | /* | 27 | /* |
| 26 | * Some useful ethtool_ops methods that're device independent. | 28 | * Some useful ethtool_ops methods that're device independent. |
| @@ -231,6 +233,29 @@ static int ethtool_set_feature_compat(struct net_device *dev, | |||
| 231 | return 1; | 233 | return 1; |
| 232 | } | 234 | } |
| 233 | 235 | ||
| 236 | static int ethtool_set_flags_compat(struct net_device *dev, | ||
| 237 | int (*legacy_set)(struct net_device *, u32), | ||
| 238 | struct ethtool_set_features_block *features, u32 mask) | ||
| 239 | { | ||
| 240 | u32 value; | ||
| 241 | |||
| 242 | if (!legacy_set) | ||
| 243 | return 0; | ||
| 244 | |||
| 245 | if (!(features[0].valid & mask)) | ||
| 246 | return 0; | ||
| 247 | |||
| 248 | value = dev->features & ~features[0].valid; | ||
| 249 | value |= features[0].requested; | ||
| 250 | |||
| 251 | features[0].valid &= ~mask; | ||
| 252 | |||
| 253 | if (legacy_set(dev, value & mask) < 0) | ||
| 254 | netdev_info(dev, "Legacy flags change failed\n"); | ||
| 255 | |||
| 256 | return 1; | ||
| 257 | } | ||
| 258 | |||
| 234 | static int ethtool_set_features_compat(struct net_device *dev, | 259 | static int ethtool_set_features_compat(struct net_device *dev, |
| 235 | struct ethtool_set_features_block *features) | 260 | struct ethtool_set_features_block *features) |
| 236 | { | 261 | { |
| @@ -247,7 +272,7 @@ static int ethtool_set_features_compat(struct net_device *dev, | |||
| 247 | features, NETIF_F_ALL_TSO); | 272 | features, NETIF_F_ALL_TSO); |
| 248 | compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_rx_csum, | 273 | compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_rx_csum, |
| 249 | features, NETIF_F_RXCSUM); | 274 | features, NETIF_F_RXCSUM); |
| 250 | compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_flags, | 275 | compat |= ethtool_set_flags_compat(dev, dev->ethtool_ops->set_flags, |
| 251 | features, flags_dup_features); | 276 | features, flags_dup_features); |
| 252 | 277 | ||
| 253 | return compat; | 278 | return compat; |
| @@ -317,7 +342,7 @@ static int ethtool_set_features(struct net_device *dev, void __user *useraddr) | |||
| 317 | 342 | ||
| 318 | dev->wanted_features &= ~features[0].valid; | 343 | dev->wanted_features &= ~features[0].valid; |
| 319 | dev->wanted_features |= features[0].valid & features[0].requested; | 344 | dev->wanted_features |= features[0].valid & features[0].requested; |
| 320 | netdev_update_features(dev); | 345 | __netdev_update_features(dev); |
| 321 | 346 | ||
| 322 | if ((dev->wanted_features ^ dev->features) & features[0].valid) | 347 | if ((dev->wanted_features ^ dev->features) & features[0].valid) |
| 323 | ret |= ETHTOOL_F_WISH; | 348 | ret |= ETHTOOL_F_WISH; |
| @@ -330,7 +355,7 @@ static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GS | |||
| 330 | /* NETIF_F_IP_CSUM */ "tx-checksum-ipv4", | 355 | /* NETIF_F_IP_CSUM */ "tx-checksum-ipv4", |
| 331 | /* NETIF_F_NO_CSUM */ "tx-checksum-unneeded", | 356 | /* NETIF_F_NO_CSUM */ "tx-checksum-unneeded", |
| 332 | /* NETIF_F_HW_CSUM */ "tx-checksum-ip-generic", | 357 | /* NETIF_F_HW_CSUM */ "tx-checksum-ip-generic", |
| 333 | /* NETIF_F_IPV6_CSUM */ "tx_checksum-ipv6", | 358 | /* NETIF_F_IPV6_CSUM */ "tx-checksum-ipv6", |
| 334 | /* NETIF_F_HIGHDMA */ "highdma", | 359 | /* NETIF_F_HIGHDMA */ "highdma", |
| 335 | /* NETIF_F_FRAGLIST */ "tx-scatter-gather-fraglist", | 360 | /* NETIF_F_FRAGLIST */ "tx-scatter-gather-fraglist", |
| 336 | /* NETIF_F_HW_VLAN_TX */ "tx-vlan-hw-insert", | 361 | /* NETIF_F_HW_VLAN_TX */ "tx-vlan-hw-insert", |
| @@ -359,8 +384,8 @@ static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GS | |||
| 359 | /* NETIF_F_NTUPLE */ "rx-ntuple-filter", | 384 | /* NETIF_F_NTUPLE */ "rx-ntuple-filter", |
| 360 | /* NETIF_F_RXHASH */ "rx-hashing", | 385 | /* NETIF_F_RXHASH */ "rx-hashing", |
| 361 | /* NETIF_F_RXCSUM */ "rx-checksum", | 386 | /* NETIF_F_RXCSUM */ "rx-checksum", |
| 362 | "", | 387 | /* NETIF_F_NOCACHE_COPY */ "tx-nocache-copy", |
| 363 | "", | 388 | /* NETIF_F_LOOPBACK */ "loopback", |
| 364 | }; | 389 | }; |
| 365 | 390 | ||
| 366 | static int __ethtool_get_sset_count(struct net_device *dev, int sset) | 391 | static int __ethtool_get_sset_count(struct net_device *dev, int sset) |
| @@ -499,7 +524,7 @@ static int ethtool_set_one_feature(struct net_device *dev, | |||
| 499 | else | 524 | else |
| 500 | dev->wanted_features &= ~mask; | 525 | dev->wanted_features &= ~mask; |
| 501 | 526 | ||
| 502 | netdev_update_features(dev); | 527 | __netdev_update_features(dev); |
| 503 | return 0; | 528 | return 0; |
| 504 | } | 529 | } |
| 505 | 530 | ||
| @@ -544,14 +569,14 @@ int __ethtool_set_flags(struct net_device *dev, u32 data) | |||
| 544 | } | 569 | } |
| 545 | 570 | ||
| 546 | /* allow changing only bits set in hw_features */ | 571 | /* allow changing only bits set in hw_features */ |
| 547 | changed = (data ^ dev->wanted_features) & flags_dup_features; | 572 | changed = (data ^ dev->features) & flags_dup_features; |
| 548 | if (changed & ~dev->hw_features) | 573 | if (changed & ~dev->hw_features) |
| 549 | return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP; | 574 | return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP; |
| 550 | 575 | ||
| 551 | dev->wanted_features = | 576 | dev->wanted_features = |
| 552 | (dev->wanted_features & ~changed) | data; | 577 | (dev->wanted_features & ~changed) | (data & dev->hw_features); |
| 553 | 578 | ||
| 554 | netdev_update_features(dev); | 579 | __netdev_update_features(dev); |
| 555 | 580 | ||
| 556 | return 0; | 581 | return 0; |
| 557 | } | 582 | } |
| @@ -908,6 +933,9 @@ static noinline_for_stack int ethtool_set_rx_ntuple(struct net_device *dev, | |||
| 908 | struct ethtool_rx_ntuple_flow_spec_container *fsc = NULL; | 933 | struct ethtool_rx_ntuple_flow_spec_container *fsc = NULL; |
| 909 | int ret; | 934 | int ret; |
| 910 | 935 | ||
| 936 | if (!ops->set_rx_ntuple) | ||
| 937 | return -EOPNOTSUPP; | ||
| 938 | |||
| 911 | if (!(dev->features & NETIF_F_NTUPLE)) | 939 | if (!(dev->features & NETIF_F_NTUPLE)) |
| 912 | return -EINVAL; | 940 | return -EINVAL; |
| 913 | 941 | ||
| @@ -1441,6 +1469,35 @@ static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr) | |||
| 1441 | return dev->ethtool_ops->set_ringparam(dev, &ringparam); | 1469 | return dev->ethtool_ops->set_ringparam(dev, &ringparam); |
| 1442 | } | 1470 | } |
| 1443 | 1471 | ||
| 1472 | static noinline_for_stack int ethtool_get_channels(struct net_device *dev, | ||
| 1473 | void __user *useraddr) | ||
| 1474 | { | ||
| 1475 | struct ethtool_channels channels = { .cmd = ETHTOOL_GCHANNELS }; | ||
| 1476 | |||
| 1477 | if (!dev->ethtool_ops->get_channels) | ||
| 1478 | return -EOPNOTSUPP; | ||
| 1479 | |||
| 1480 | dev->ethtool_ops->get_channels(dev, &channels); | ||
| 1481 | |||
| 1482 | if (copy_to_user(useraddr, &channels, sizeof(channels))) | ||
| 1483 | return -EFAULT; | ||
| 1484 | return 0; | ||
| 1485 | } | ||
| 1486 | |||
| 1487 | static noinline_for_stack int ethtool_set_channels(struct net_device *dev, | ||
| 1488 | void __user *useraddr) | ||
| 1489 | { | ||
| 1490 | struct ethtool_channels channels; | ||
| 1491 | |||
| 1492 | if (!dev->ethtool_ops->set_channels) | ||
| 1493 | return -EOPNOTSUPP; | ||
| 1494 | |||
| 1495 | if (copy_from_user(&channels, useraddr, sizeof(channels))) | ||
| 1496 | return -EFAULT; | ||
| 1497 | |||
| 1498 | return dev->ethtool_ops->set_channels(dev, &channels); | ||
| 1499 | } | ||
| 1500 | |||
| 1444 | static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr) | 1501 | static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr) |
| 1445 | { | 1502 | { |
| 1446 | struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM }; | 1503 | struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM }; |
| @@ -1618,14 +1675,60 @@ out: | |||
| 1618 | static int ethtool_phys_id(struct net_device *dev, void __user *useraddr) | 1675 | static int ethtool_phys_id(struct net_device *dev, void __user *useraddr) |
| 1619 | { | 1676 | { |
| 1620 | struct ethtool_value id; | 1677 | struct ethtool_value id; |
| 1678 | static bool busy; | ||
| 1679 | int rc; | ||
| 1621 | 1680 | ||
| 1622 | if (!dev->ethtool_ops->phys_id) | 1681 | if (!dev->ethtool_ops->set_phys_id) |
| 1623 | return -EOPNOTSUPP; | 1682 | return -EOPNOTSUPP; |
| 1624 | 1683 | ||
| 1684 | if (busy) | ||
| 1685 | return -EBUSY; | ||
| 1686 | |||
| 1625 | if (copy_from_user(&id, useraddr, sizeof(id))) | 1687 | if (copy_from_user(&id, useraddr, sizeof(id))) |
| 1626 | return -EFAULT; | 1688 | return -EFAULT; |
| 1627 | 1689 | ||
| 1628 | return dev->ethtool_ops->phys_id(dev, id.data); | 1690 | rc = dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_ACTIVE); |
| 1691 | if (rc < 0) | ||
| 1692 | return rc; | ||
| 1693 | |||
| 1694 | /* Drop the RTNL lock while waiting, but prevent reentry or | ||
| 1695 | * removal of the device. | ||
| 1696 | */ | ||
| 1697 | busy = true; | ||
| 1698 | dev_hold(dev); | ||
| 1699 | rtnl_unlock(); | ||
| 1700 | |||
| 1701 | if (rc == 0) { | ||
| 1702 | /* Driver will handle this itself */ | ||
| 1703 | schedule_timeout_interruptible( | ||
| 1704 | id.data ? (id.data * HZ) : MAX_SCHEDULE_TIMEOUT); | ||
| 1705 | } else { | ||
| 1706 | /* Driver expects to be called at twice the frequency in rc */ | ||
| 1707 | int n = rc * 2, i, interval = HZ / n; | ||
| 1708 | |||
| 1709 | /* Count down seconds */ | ||
| 1710 | do { | ||
| 1711 | /* Count down iterations per second */ | ||
| 1712 | i = n; | ||
| 1713 | do { | ||
| 1714 | rtnl_lock(); | ||
| 1715 | rc = dev->ethtool_ops->set_phys_id(dev, | ||
| 1716 | (i & 1) ? ETHTOOL_ID_OFF : ETHTOOL_ID_ON); | ||
| 1717 | rtnl_unlock(); | ||
| 1718 | if (rc) | ||
| 1719 | break; | ||
| 1720 | schedule_timeout_interruptible(interval); | ||
| 1721 | } while (!signal_pending(current) && --i != 0); | ||
| 1722 | } while (!signal_pending(current) && | ||
| 1723 | (id.data == 0 || --id.data != 0)); | ||
| 1724 | } | ||
| 1725 | |||
| 1726 | rtnl_lock(); | ||
| 1727 | dev_put(dev); | ||
| 1728 | busy = false; | ||
| 1729 | |||
| 1730 | (void)dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE); | ||
| 1731 | return rc; | ||
| 1629 | } | 1732 | } |
| 1630 | 1733 | ||
| 1631 | static int ethtool_get_stats(struct net_device *dev, void __user *useraddr) | 1734 | static int ethtool_get_stats(struct net_device *dev, void __user *useraddr) |
| @@ -1743,6 +1846,87 @@ static noinline_for_stack int ethtool_flash_device(struct net_device *dev, | |||
| 1743 | return dev->ethtool_ops->flash_device(dev, &efl); | 1846 | return dev->ethtool_ops->flash_device(dev, &efl); |
| 1744 | } | 1847 | } |
| 1745 | 1848 | ||
| 1849 | static int ethtool_set_dump(struct net_device *dev, | ||
| 1850 | void __user *useraddr) | ||
| 1851 | { | ||
| 1852 | struct ethtool_dump dump; | ||
| 1853 | |||
| 1854 | if (!dev->ethtool_ops->set_dump) | ||
| 1855 | return -EOPNOTSUPP; | ||
| 1856 | |||
| 1857 | if (copy_from_user(&dump, useraddr, sizeof(dump))) | ||
| 1858 | return -EFAULT; | ||
| 1859 | |||
| 1860 | return dev->ethtool_ops->set_dump(dev, &dump); | ||
| 1861 | } | ||
| 1862 | |||
| 1863 | static int ethtool_get_dump_flag(struct net_device *dev, | ||
| 1864 | void __user *useraddr) | ||
| 1865 | { | ||
| 1866 | int ret; | ||
| 1867 | struct ethtool_dump dump; | ||
| 1868 | const struct ethtool_ops *ops = dev->ethtool_ops; | ||
| 1869 | |||
| 1870 | if (!dev->ethtool_ops->get_dump_flag) | ||
| 1871 | return -EOPNOTSUPP; | ||
| 1872 | |||
| 1873 | if (copy_from_user(&dump, useraddr, sizeof(dump))) | ||
| 1874 | return -EFAULT; | ||
| 1875 | |||
| 1876 | ret = ops->get_dump_flag(dev, &dump); | ||
| 1877 | if (ret) | ||
| 1878 | return ret; | ||
| 1879 | |||
| 1880 | if (copy_to_user(useraddr, &dump, sizeof(dump))) | ||
| 1881 | return -EFAULT; | ||
| 1882 | return 0; | ||
| 1883 | } | ||
| 1884 | |||
| 1885 | static int ethtool_get_dump_data(struct net_device *dev, | ||
| 1886 | void __user *useraddr) | ||
| 1887 | { | ||
| 1888 | int ret; | ||
| 1889 | __u32 len; | ||
| 1890 | struct ethtool_dump dump, tmp; | ||
| 1891 | const struct ethtool_ops *ops = dev->ethtool_ops; | ||
| 1892 | void *data = NULL; | ||
| 1893 | |||
| 1894 | if (!dev->ethtool_ops->get_dump_data || | ||
| 1895 | !dev->ethtool_ops->get_dump_flag) | ||
| 1896 | return -EOPNOTSUPP; | ||
| 1897 | |||
| 1898 | if (copy_from_user(&dump, useraddr, sizeof(dump))) | ||
| 1899 | return -EFAULT; | ||
| 1900 | |||
| 1901 | memset(&tmp, 0, sizeof(tmp)); | ||
| 1902 | tmp.cmd = ETHTOOL_GET_DUMP_FLAG; | ||
| 1903 | ret = ops->get_dump_flag(dev, &tmp); | ||
| 1904 | if (ret) | ||
| 1905 | return ret; | ||
| 1906 | |||
| 1907 | len = (tmp.len > dump.len) ? dump.len : tmp.len; | ||
| 1908 | if (!len) | ||
| 1909 | return -EFAULT; | ||
| 1910 | |||
| 1911 | data = vzalloc(tmp.len); | ||
| 1912 | if (!data) | ||
| 1913 | return -ENOMEM; | ||
| 1914 | ret = ops->get_dump_data(dev, &dump, data); | ||
| 1915 | if (ret) | ||
| 1916 | goto out; | ||
| 1917 | |||
| 1918 | if (copy_to_user(useraddr, &dump, sizeof(dump))) { | ||
| 1919 | ret = -EFAULT; | ||
| 1920 | goto out; | ||
| 1921 | } | ||
| 1922 | useraddr += offsetof(struct ethtool_dump, data); | ||
| 1923 | if (copy_to_user(useraddr, data, len)) | ||
| 1924 | ret = -EFAULT; | ||
| 1925 | out: | ||
| 1926 | vfree(data); | ||
| 1927 | return ret; | ||
| 1928 | } | ||
| 1929 | |||
| 1746 | /* The main entry point in this file. Called from net/core/dev.c */ | 1930 | /* The main entry point in this file. Called from net/core/dev.c */ |
| 1747 | 1931 | ||
| 1748 | int dev_ethtool(struct net *net, struct ifreq *ifr) | 1932 | int dev_ethtool(struct net *net, struct ifreq *ifr) |
| @@ -1953,6 +2137,21 @@ int dev_ethtool(struct net *net, struct ifreq *ifr) | |||
| 1953 | case ETHTOOL_SGRO: | 2137 | case ETHTOOL_SGRO: |
| 1954 | rc = ethtool_set_one_feature(dev, useraddr, ethcmd); | 2138 | rc = ethtool_set_one_feature(dev, useraddr, ethcmd); |
| 1955 | break; | 2139 | break; |
| 2140 | case ETHTOOL_GCHANNELS: | ||
| 2141 | rc = ethtool_get_channels(dev, useraddr); | ||
| 2142 | break; | ||
| 2143 | case ETHTOOL_SCHANNELS: | ||
| 2144 | rc = ethtool_set_channels(dev, useraddr); | ||
| 2145 | break; | ||
| 2146 | case ETHTOOL_SET_DUMP: | ||
| 2147 | rc = ethtool_set_dump(dev, useraddr); | ||
| 2148 | break; | ||
| 2149 | case ETHTOOL_GET_DUMP_FLAG: | ||
| 2150 | rc = ethtool_get_dump_flag(dev, useraddr); | ||
| 2151 | break; | ||
| 2152 | case ETHTOOL_GET_DUMP_DATA: | ||
| 2153 | rc = ethtool_get_dump_data(dev, useraddr); | ||
| 2154 | break; | ||
| 1956 | default: | 2155 | default: |
| 1957 | rc = -EOPNOTSUPP; | 2156 | rc = -EOPNOTSUPP; |
| 1958 | } | 2157 | } |
