// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 1999 - 2020 Intel Corporation. */ /* * net/core/ethtool.c - Ethtool ioctl handler * Copyright (c) 2003 Matthew Wilcox * * This file is where we call all the ethtool_ops commands to get * the information ethtool needs. We fall back to calling do_ioctl() * for drivers which haven't been converted to ethtool_ops yet. * * It's GPL, stupid. * * Modification by sfeldma@pobox.com to work as backward compat * solution for pre-ethtool_ops kernels. * - copied struct ethtool_ops from ethtool.h * - defined SET_ETHTOOL_OPS * - put in some #ifndef NETIF_F_xxx wrappers * - changes refs to dev->ethtool_ops to ethtool_ops * - changed dev_ethtool to ethtool_ioctl * - remove EXPORT_SYMBOL()s * - added _kc_ prefix in built-in ethtool_op_xxx ops. */ #include #include #include #include #include #include #include #include "kcompat.h" #undef SUPPORTED_10000baseT_Full #define SUPPORTED_10000baseT_Full (1 << 12) #undef ADVERTISED_10000baseT_Full #define ADVERTISED_10000baseT_Full (1 << 12) #undef SPEED_10000 #undef ethtool_ops #define ethtool_ops _kc_ethtool_ops struct _kc_ethtool_ops { int (*get_settings) (struct net_device *, struct ethtool_cmd *); int (*set_settings) (struct net_device *, struct ethtool_cmd *); void (*get_drvinfo) (struct net_device *, struct ethtool_drvinfo *); int (*get_regs_len) (struct net_device *); void (*get_regs) (struct net_device *, struct ethtool_regs *, void *); void (*get_wol) (struct net_device *, struct ethtool_wolinfo *); int (*set_wol) (struct net_device *, struct ethtool_wolinfo *); u32 (*get_msglevel) (struct net_device *); void (*set_msglevel) (struct net_device *, u32); int (*nway_reset) (struct net_device *); u32 (*get_link) (struct net_device *); int (*get_eeprom_len) (struct net_device *); int (*get_eeprom) (struct net_device *, struct ethtool_eeprom *, u8 *); int (*set_eeprom) (struct net_device *, struct ethtool_eeprom *, u8 *); int (*get_coalesce) (struct net_device *, struct ethtool_coalesce *); int (*set_coalesce) (struct net_device *, struct ethtool_coalesce *); void (*get_ringparam) (struct net_device *, struct ethtool_ringparam *); int (*set_ringparam) (struct net_device *, struct ethtool_ringparam *); void (*get_pauseparam) (struct net_device *, struct ethtool_pauseparam *); int (*set_pauseparam) (struct net_device *, struct ethtool_pauseparam *); u32 (*get_rx_csum) (struct net_device *); int (*set_rx_csum) (struct net_device *, u32); u32 (*get_tx_csum) (struct net_device *); int (*set_tx_csum) (struct net_device *, u32); u32 (*get_sg) (struct net_device *); int (*set_sg) (struct net_device *, u32); u32 (*get_tso) (struct net_device *); int (*set_tso) (struct net_device *, u32); int (*self_test_count) (struct net_device *); void (*self_test) (struct net_device *, struct ethtool_test *, u64 *); void (*get_strings) (struct net_device *, u32 stringset, u8 *); int (*phys_id) (struct net_device *, u32); int (*get_stats_count) (struct net_device *); void (*get_ethtool_stats) (struct net_device *, struct ethtool_stats *, u64 *); } *ethtool_ops = NULL; #undef SET_ETHTOOL_OPS #define SET_ETHTOOL_OPS(netdev, ops) (ethtool_ops = (ops)) /* * Some useful ethtool_ops methods that are device independent. If we find that * all drivers want to do the same thing here, we can turn these into dev_() * function calls. */ #undef ethtool_op_get_link #define ethtool_op_get_link _kc_ethtool_op_get_link u32 _kc_ethtool_op_get_link(struct net_device *dev) { return netif_carrier_ok(dev) ? 1 : 0; } #undef ethtool_op_get_tx_csum #define ethtool_op_get_tx_csum _kc_ethtool_op_get_tx_csum u32 _kc_ethtool_op_get_tx_csum(struct net_device *dev) { #ifdef NETIF_F_IP_CSUM return (dev->features & NETIF_F_IP_CSUM) != 0; #else return 0; #endif } #undef ethtool_op_set_tx_csum #define ethtool_op_set_tx_csum _kc_ethtool_op_set_tx_csum int _kc_ethtool_op_set_tx_csum(struct net_device *dev, u32 data) { #ifdef NETIF_F_IP_CSUM if (data) #ifdef NETIF_F_IPV6_CSUM dev->features |= (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM); else dev->features &= ~(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM); #else dev->features |= NETIF_F_IP_CSUM; else dev->features &= ~NETIF_F_IP_CSUM; #endif #endif return 0; } #undef ethtool_op_get_sg #define ethtool_op_get_sg _kc_ethtool_op_get_sg u32 _kc_ethtool_op_get_sg(struct net_device *dev) { #ifdef NETIF_F_SG return (dev->features & NETIF_F_SG) != 0; #else return 0; #endif } #undef ethtool_op_set_sg #define ethtool_op_set_sg _kc_ethtool_op_set_sg int _kc_ethtool_op_set_sg(struct net_device *dev, u32 data) { #ifdef NETIF_F_SG if (data) dev->features |= NETIF_F_SG; else dev->features &= ~NETIF_F_SG; #endif return 0; } #undef ethtool_op_get_tso #define ethtool_op_get_tso _kc_ethtool_op_get_tso u32 _kc_ethtool_op_get_tso(struct net_device *dev) { #ifdef NETIF_F_TSO return (dev->features & NETIF_F_TSO) != 0; #else return 0; #endif } #undef ethtool_op_set_tso #define ethtool_op_set_tso _kc_ethtool_op_set_tso int _kc_ethtool_op_set_tso(struct net_device *dev, u32 data) { #ifdef NETIF_F_TSO if (data) dev->features |= NETIF_F_TSO; else dev->features &= ~NETIF_F_TSO; #endif return 0; } /* Handlers for each ethtool command */ static int ethtool_get_settings(struct net_device *dev, void *useraddr) { struct ethtool_cmd cmd = { ETHTOOL_GSET }; int err; if (!ethtool_ops->get_settings) return -EOPNOTSUPP; err = ethtool_ops->get_settings(dev, &cmd); if (err < 0) return err; if (copy_to_user(useraddr, &cmd, sizeof(cmd))) return -EFAULT; return 0; } static int ethtool_set_settings(struct net_device *dev, void *useraddr) { struct ethtool_cmd cmd; if (!ethtool_ops->set_settings) return -EOPNOTSUPP; if (copy_from_user(&cmd, useraddr, sizeof(cmd))) return -EFAULT; return ethtool_ops->set_settings(dev, &cmd); } static int ethtool_get_drvinfo(struct net_device *dev, void *useraddr) { struct ethtool_drvinfo info; struct ethtool_ops *ops = ethtool_ops; if (!ops->get_drvinfo) return -EOPNOTSUPP; memset(&info, 0, sizeof(info)); info.cmd = ETHTOOL_GDRVINFO; ops->get_drvinfo(dev, &info); if (ops->self_test_count) info.testinfo_len = ops->self_test_count(dev); if (ops->get_stats_count) info.n_stats = ops->get_stats_count(dev); if (ops->get_regs_len) info.regdump_len = ops->get_regs_len(dev); if (ops->get_eeprom_len) info.eedump_len = ops->get_eeprom_len(dev); if (copy_to_user(useraddr, &info, sizeof(info))) return -EFAULT; return 0; } static int ethtool_get_regs(struct net_device *dev, char *useraddr) { struct ethtool_regs regs; struct ethtool_ops *ops = ethtool_ops; void *regbuf; int reglen, ret; if (!ops->get_regs || !ops->get_regs_len) return -EOPNOTSUPP; if (copy_from_user(®s, useraddr, sizeof(regs))) return -EFAULT; reglen = ops->get_regs_len(dev); if (regs.len > reglen) regs.len = reglen; regbuf = kmalloc(reglen, GFP_USER); if (!regbuf) return -ENOMEM; ops->get_regs(dev, ®s, regbuf); ret = -EFAULT; if (copy_to_user(useraddr, ®s, sizeof(regs))) goto out; useraddr += offsetof(struct ethtool_regs, data); if (copy_to_user(useraddr, regbuf, reglen)) goto out; ret = 0; out: kfree(regbuf); return ret; } static int ethtool_get_wol(struct net_device *dev, char *useraddr) { struct ethtool_wolinfo wol = { ETHTOOL_GWOL }; if (!ethtool_ops->get_wol) return -EOPNOTSUPP; ethtool_ops->get_wol(dev, &wol); if (copy_to_user(useraddr, &wol, sizeof(wol))) return -EFAULT; return 0; } static int ethtool_set_wol(struct net_device *dev, char *useraddr) { struct ethtool_wolinfo wol; if (!ethtool_ops->set_wol) return -EOPNOTSUPP; if (copy_from_user(&wol, useraddr, sizeof(wol))) return -EFAULT; return ethtool_ops->set_wol(dev, &wol); } static int ethtool_get_msglevel(struct net_device *dev, char *useraddr) { struct ethtool_value edata = { ETHTOOL_GMSGLVL }; if (!ethtool_ops->get_msglevel) return -EOPNOTSUPP; edata.data = ethtool_ops->get_msglevel(dev); if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0; } static int ethtool_set_msglevel(struct net_device *dev, char *useraddr) { struct ethtool_value edata; if (!ethtool_ops->set_msglevel) return -EOPNOTSUPP; if (copy_from_user(&edata, useraddr, sizeof(edata))) return -EFAULT; ethtool_ops->set_msglevel(dev, edata.data); return 0; } static int ethtool_nway_reset(struct net_device *dev) { if (!ethtool_ops->nway_reset) return -EOPNOTSUPP; return ethtool_ops->nway_reset(dev); } static int ethtool_get_link(struct net_device *dev, void *useraddr) { struct ethtool_value edata = { ETHTOOL_GLINK }; if (!ethtool_ops->get_link) return -EOPNOTSUPP; edata.data = ethtool_ops->get_link(dev); if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0; } static int ethtool_get_eeprom(struct net_device *dev, void *useraddr) { struct ethtool_eeprom eeprom; struct ethtool_ops *ops = ethtool_ops; u8 *data; int ret; if (!ops->get_eeprom || !ops->get_eeprom_len) return -EOPNOTSUPP; if (copy_from_user(&eeprom, useraddr, sizeof(eeprom))) return -EFAULT; /* Check for wrap and zero */ if (eeprom.offset + eeprom.len <= eeprom.offset) return -EINVAL; /* Check for exceeding total eeprom len */ if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev)) return -EINVAL; data = kmalloc(eeprom.len, GFP_USER); if (!data) return -ENOMEM; ret = -EFAULT; if (copy_from_user(data, useraddr + sizeof(eeprom), eeprom.len)) goto out; ret = ops->get_eeprom(dev, &eeprom, data); if (ret) goto out; ret = -EFAULT; if (copy_to_user(useraddr, &eeprom, sizeof(eeprom))) goto out; if (copy_to_user(useraddr + sizeof(eeprom), data, eeprom.len)) goto out; ret = 0; out: kfree(data); return ret; } static int ethtool_set_eeprom(struct net_device *dev, void *useraddr) { struct ethtool_eeprom eeprom; struct ethtool_ops *ops = ethtool_ops; u8 *data; int ret; if (!ops->set_eeprom || !ops->get_eeprom_len) return -EOPNOTSUPP; if (copy_from_user(&eeprom, useraddr, sizeof(eeprom))) return -EFAULT; /* Check for wrap and zero */ if (eeprom.offset + eeprom.len <= eeprom.offset) return -EINVAL; /* Check for exceeding total eeprom len */ if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev)) return -EINVAL; data = kmalloc(eeprom.len, GFP_USER); if (!data) return -ENOMEM; ret = -EFAULT; if (copy_from_user(data, useraddr + sizeof(eeprom), eeprom.len)) goto out; ret = ops->set_eeprom(dev, &eeprom, data); if (ret) goto out; if (copy_to_user(useraddr + sizeof(eeprom), data, eeprom.len)) ret = -EFAULT; out: kfree(data); return ret; } static int ethtool_get_coalesce(struct net_device *dev, void *useraddr) { struct ethtool_coalesce coalesce = { ETHTOOL_GCOALESCE }; if (!ethtool_ops->get_coalesce) return -EOPNOTSUPP; ethtool_ops->get_coalesce(dev, &coalesce); if (copy_to_user(useraddr, &coalesce, sizeof(coalesce))) return -EFAULT; return 0; } static int ethtool_set_coalesce(struct net_device *dev, void *useraddr) { struct ethtool_coalesce coalesce; if (!ethtool_ops->get_coalesce) return -EOPNOTSUPP; if (copy_from_user(&coalesce, useraddr, sizeof(coalesce))) return -EFAULT; return ethtool_ops->set_coalesce(dev, &coalesce); } static int ethtool_get_ringparam(struct net_device *dev, void *useraddr) { struct ethtool_ringparam ringparam = { ETHTOOL_GRINGPARAM }; if (!ethtool_ops->get_ringparam) return -EOPNOTSUPP; ethtool_ops->get_ringparam(dev, &ringparam); if (copy_to_user(useraddr, &ringparam, sizeof(ringparam))) return -EFAULT; return 0; } static int ethtool_set_ringparam(struct net_device *dev, void *useraddr) { struct ethtool_ringparam ringparam; if (!ethtool_ops->get_ringparam) return -EOPNOTSUPP; if (copy_from_user(&ringparam, useraddr, sizeof(ringparam))) return -EFAULT; return ethtool_ops->set_ringparam(dev, &ringparam); } static int ethtool_get_pauseparam(struct net_device *dev, void *useraddr) { struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM }; if (!ethtool_ops->get_pauseparam) return -EOPNOTSUPP; ethtool_ops->get_pauseparam(dev, &pauseparam); if (copy_to_user(useraddr, &pauseparam, sizeof(pauseparam))) return -EFAULT; return 0; } static int ethtool_set_pauseparam(struct net_device *dev, void *useraddr) { struct ethtool_pauseparam pauseparam; if (!ethtool_ops->get_pauseparam) return -EOPNOTSUPP; if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam))) return -EFAULT; return ethtool_ops->set_pauseparam(dev, &pauseparam); } static int ethtool_get_rx_csum(struct net_device *dev, char *useraddr) { struct ethtool_value edata = { ETHTOOL_GRXCSUM }; if (!ethtool_ops->get_rx_csum) return -EOPNOTSUPP; edata.data = ethtool_ops->get_rx_csum(dev); if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0; } static int ethtool_set_rx_csum(struct net_device *dev, char *useraddr) { struct ethtool_value edata; if (!ethtool_ops->set_rx_csum) return -EOPNOTSUPP; if (copy_from_user(&edata, useraddr, sizeof(edata))) return -EFAULT; ethtool_ops->set_rx_csum(dev, edata.data); return 0; } static int ethtool_get_tx_csum(struct net_device *dev, char *useraddr) { struct ethtool_value edata = { ETHTOOL_GTXCSUM }; if (!ethtool_ops->get_tx_csum) return -EOPNOTSUPP; edata.data = ethtool_ops->get_tx_csum(dev); if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0; } static int ethtool_set_tx_csum(struct net_device *dev, char *useraddr) { struct ethtool_value edata; if (!ethtool_ops->set_tx_csum) return -EOPNOTSUPP; if (copy_from_user(&edata, useraddr, sizeof(edata))) return -EFAULT; return ethtool_ops->set_tx_csum(dev, edata.data); } static int ethtool_get_sg(struct net_device *dev, char *useraddr) { struct ethtool_value edata = { ETHTOOL_GSG }; if (!ethtool_ops->get_sg) return -EOPNOTSUPP; edata.data = ethtool_ops->get_sg(dev); if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0; } static int ethtool_set_sg(struct net_device *dev, char *useraddr) { struct ethtool_value edata; if (!ethtool_ops->set_sg) return -EOPNOTSUPP; if (copy_from_user(&edata, useraddr, sizeof(edata))) return -EFAULT; return ethtool_ops->set_sg(dev, edata.data); } static int ethtool_get_tso(struct net_device *dev, char *useraddr) { struct ethtool_value edata = { ETHTOOL_GTSO }; if (!ethtool_ops->get_tso) return -EOPNOTSUPP; edata.data = ethtool_ops->get_tso(dev); if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0; } static int ethtool_set_tso(struct net_device *dev, char *useraddr) { struct ethtool_value edata; if (!ethtool_ops->set_tso) return -EOPNOTSUPP; if (copy_from_user(&edata, useraddr, sizeof(edata))) return -EFAULT; return ethtool_ops->set_tso(dev, edata.data); } static int ethtool_self_test(struct net_device *dev, char *useraddr) { struct ethtool_test test; struct ethtool_ops *ops = ethtool_ops; u64 *data; int ret; if (!ops->self_test || !ops->self_test_count) return -EOPNOTSUPP; if (copy_from_user(&test, useraddr, sizeof(test))) return -EFAULT; test.len = ops->self_test_count(dev); data = kmalloc(test.len * sizeof(u64), GFP_USER); if (!data) return -ENOMEM; ops->self_test(dev, &test, data); ret = -EFAULT; if (copy_to_user(useraddr, &test, sizeof(test))) goto out; useraddr += sizeof(test); if (copy_to_user(useraddr, data, test.len * sizeof(u64))) goto out; ret = 0; out: kfree(data); return ret; } static int ethtool_get_strings(struct net_device *dev, void *useraddr) { struct ethtool_gstrings gstrings; struct ethtool_ops *ops = ethtool_ops; u8 *data; int ret; if (!ops->get_strings) return -EOPNOTSUPP; if (copy_from_user(&gstrings, useraddr, sizeof(gstrings))) return -EFAULT; switch (gstrings.string_set) { case ETH_SS_TEST: if (!ops->self_test_count) return -EOPNOTSUPP; gstrings.len = ops->self_test_count(dev); break; case ETH_SS_STATS: if (!ops->get_stats_count) return -EOPNOTSUPP; gstrings.len = ops->get_stats_count(dev); break; default: return -EINVAL; } data = kmalloc(gstrings.len * ETH_GSTRING_LEN, GFP_USER); if (!data) return -ENOMEM; ops->get_strings(dev, gstrings.string_set, data); ret = -EFAULT; if (copy_to_user(useraddr, &gstrings, sizeof(gstrings))) goto out; useraddr += sizeof(gstrings); if (copy_to_user(useraddr, data, gstrings.len * ETH_GSTRING_LEN)) goto out; ret = 0; out: kfree(data); return ret; } static int ethtool_phys_id(struct net_device *dev, void *useraddr) { struct ethtool_value id; if (!ethtool_ops->phys_id) return -EOPNOTSUPP; if (copy_from_user(&id, useraddr, sizeof(id))) return -EFAULT; return ethtool_ops->phys_id(dev, id.data); } static int ethtool_get_stats(struct net_device *dev, void *useraddr) { struct ethtool_stats stats; struct ethtool_ops *ops = ethtool_ops; u64 *data; int ret; if (!ops->get_ethtool_stats || !ops->get_stats_count) return -EOPNOTSUPP; if (copy_from_user(&stats, useraddr, sizeof(stats))) return -EFAULT; stats.n_stats = ops->get_stats_count(dev); data = kmalloc(stats.n_stats * sizeof(u64), GFP_USER); if (!data) return -ENOMEM; ops->get_ethtool_stats(dev, &stats, data); ret = -EFAULT; if (copy_to_user(useraddr, &stats, sizeof(stats))) goto out; useraddr += sizeof(stats); if (copy_to_user(useraddr, data, stats.n_stats * sizeof(u64))) goto out; ret = 0; out: kfree(data); return ret; } /* The main entry point in this file. Called from net/core/dev.c */ #define ETHTOOL_OPS_COMPAT int ethtool_ioctl(struct ifreq *ifr) { struct net_device *dev = __dev_get_by_name(ifr->ifr_name); void *useraddr = (void *)ifr->ifr_data; u32 ethcmd; /* * XXX: This can be pushed down into the ethtool_* handlers that * need it. Keep existing behavior for the moment. */ if (!capable(CAP_NET_ADMIN)) return -EPERM; if (!dev || !netif_device_present(dev)) return -ENODEV; if (copy_from_user(ðcmd, useraddr, sizeof(ethcmd))) return -EFAULT; switch (ethcmd) { case ETHTOOL_GSET: return ethtool_get_settings(dev, useraddr); case ETHTOOL_SSET: return ethtool_set_settings(dev, useraddr); case ETHTOOL_GDRVINFO: return ethtool_get_drvinfo(dev, useraddr); case ETHTOOL_GREGS: return ethtool_get_regs(dev, useraddr); case ETHTOOL_GWOL: return ethtool_get_wol(dev, useraddr); case ETHTOOL_SWOL: return ethtool_set_wol(dev, useraddr); case ETHTOOL_GMSGLVL: return ethtool_get_msglevel(dev, useraddr); case ETHTOOL_SMSGLVL: return ethtool_set_msglevel(dev, useraddr); case ETHTOOL_NWAY_RST: return ethtool_nway_reset(dev); case ETHTOOL_GLINK: return ethtool_get_link(dev, useraddr); case ETHTOOL_GEEPROM: return ethtool_get_eeprom(dev, useraddr); case ETHTOOL_SEEPROM: return ethtool_set_eeprom(dev, useraddr); case ETHTOOL_GCOALESCE: return ethtool_get_coalesce(dev, useraddr); case ETHTOOL_SCOALESCE: return ethtool_set_coalesce(dev, useraddr); case ETHTOOL_GRINGPARAM: return ethtool_get_ringparam(dev, useraddr); case ETHTOOL_SRINGPARAM: return ethtool_set_ringparam(dev, useraddr); case ETHTOOL_GPAUSEPARAM: return ethtool_get_pauseparam(dev, useraddr); case ETHTOOL_SPAUSEPARAM: return ethtool_set_pauseparam(dev, useraddr); case ETHTOOL_GRXCSUM: return ethtool_get_rx_csum(dev, useraddr); case ETHTOOL_SRXCSUM: return ethtool_set_rx_csum(dev, useraddr); case ETHTOOL_GTXCSUM: return ethtool_get_tx_csum(dev, useraddr); case ETHTOOL_STXCSUM: return ethtool_set_tx_csum(dev, useraddr); case ETHTOOL_GSG: return ethtool_get_sg(dev, useraddr); case ETHTOOL_SSG: return ethtool_set_sg(dev, useraddr); case ETHTOOL_GTSO: return ethtool_get_tso(dev, useraddr); case ETHTOOL_STSO: return ethtool_set_tso(dev, useraddr); case ETHTOOL_TEST: return ethtool_self_test(dev, useraddr); case ETHTOOL_GSTRINGS: return ethtool_get_strings(dev, useraddr); case ETHTOOL_PHYS_ID: return ethtool_phys_id(dev, useraddr); case ETHTOOL_GSTATS: return ethtool_get_stats(dev, useraddr); default: return -EOPNOTSUPP; } return -EOPNOTSUPP; } #define mii_if_info _kc_mii_if_info struct _kc_mii_if_info { int phy_id; int advertising; int phy_id_mask; int reg_num_mask; unsigned int full_duplex:1; /* is full duplex? */ unsigned int force_media:1; /* is autoneg. disabled? */ struct net_device *dev; int (*mdio_read) (struct net_device * dev, int phy_id, int location); void (*mdio_write) (struct net_device * dev, int phy_id, int location, int val); }; struct ethtool_cmd; struct mii_ioctl_data; #undef mii_link_ok #define mii_link_ok _kc_mii_link_ok #undef mii_nway_restart #define mii_nway_restart _kc_mii_nway_restart #undef mii_ethtool_gset #define mii_ethtool_gset _kc_mii_ethtool_gset #undef mii_ethtool_sset #define mii_ethtool_sset _kc_mii_ethtool_sset #undef mii_check_link #define mii_check_link _kc_mii_check_link extern int _kc_mii_link_ok(struct mii_if_info *mii); extern int _kc_mii_nway_restart(struct mii_if_info *mii); extern int _kc_mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd); extern int _kc_mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd); extern void _kc_mii_check_link(struct mii_if_info *mii); #if ( LINUX_VERSION_CODE > KERNEL_VERSION(2,4,6) ) #undef generic_mii_ioctl #define generic_mii_ioctl _kc_generic_mii_ioctl extern int _kc_generic_mii_ioctl(struct mii_if_info *mii_if, struct mii_ioctl_data *mii_data, int cmd, unsigned int *duplex_changed); #endif /* > 2.4.6 */ struct _kc_pci_dev_ext { struct pci_dev *dev; void *pci_drvdata; struct pci_driver *driver; }; struct _kc_net_dev_ext { struct net_device *dev; unsigned int carrier; }; /**************************************/ /* mii support */ int _kc_mii_ethtool_gset(struct mii_if_info *mii, struct ethtool_cmd *ecmd) { struct net_device *dev = mii->dev; u32 advert, bmcr, lpa, nego; ecmd->supported = (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full | SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII); /* only supports twisted-pair */ ecmd->port = PORT_MII; /* only supports internal transceiver */ ecmd->transceiver = XCVR_INTERNAL; /* this isn't fully supported at higher layers */ ecmd->phy_address = mii->phy_id; ecmd->advertising = ADVERTISED_TP | ADVERTISED_MII; advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE); if (advert & ADVERTISE_10HALF) ecmd->advertising |= ADVERTISED_10baseT_Half; if (advert & ADVERTISE_10FULL) ecmd->advertising |= ADVERTISED_10baseT_Full; if (advert & ADVERTISE_100HALF) ecmd->advertising |= ADVERTISED_100baseT_Half; if (advert & ADVERTISE_100FULL) ecmd->advertising |= ADVERTISED_100baseT_Full; bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); lpa = mii->mdio_read(dev, mii->phy_id, MII_LPA); if (bmcr & BMCR_ANENABLE) { ecmd->advertising |= ADVERTISED_Autoneg; ecmd->autoneg = AUTONEG_ENABLE; nego = mii_nway_result(advert & lpa); if (nego == LPA_100FULL || nego == LPA_100HALF) ecmd->speed = SPEED_100; else ecmd->speed = SPEED_10; if (nego == LPA_100FULL || nego == LPA_10FULL) { ecmd->duplex = DUPLEX_FULL; mii->full_duplex = 1; } else { ecmd->duplex = DUPLEX_HALF; mii->full_duplex = 0; } } else { ecmd->autoneg = AUTONEG_DISABLE; ecmd->speed = (bmcr & BMCR_SPEED100) ? SPEED_100 : SPEED_10; ecmd->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF; } /* ignore maxtxpkt, maxrxpkt for now */ return 0; } int _kc_mii_ethtool_sset(struct mii_if_info *mii, struct ethtool_cmd *ecmd) { struct net_device *dev = mii->dev; if (ecmd->speed != SPEED_10 && ecmd->speed != SPEED_100) return -EINVAL; if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL) return -EINVAL; if (ecmd->port != PORT_MII) return -EINVAL; if (ecmd->transceiver != XCVR_INTERNAL) return -EINVAL; if (ecmd->phy_address != mii->phy_id) return -EINVAL; if (ecmd->autoneg != AUTONEG_DISABLE && ecmd->autoneg != AUTONEG_ENABLE) return -EINVAL; /* ignore supported, maxtxpkt, maxrxpkt */ if (ecmd->autoneg == AUTONEG_ENABLE) { u32 bmcr, advert, tmp; if ((ecmd->advertising & (ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full | ADVERTISED_100baseT_Half | ADVERTISED_100baseT_Full)) == 0) return -EINVAL; /* advertise only what has been requested */ advert = mii->mdio_read(dev, mii->phy_id, MII_ADVERTISE); tmp = advert & ~(ADVERTISE_ALL | ADVERTISE_100BASE4); if (ADVERTISED_10baseT_Half) tmp |= ADVERTISE_10HALF; if (ADVERTISED_10baseT_Full) tmp |= ADVERTISE_10FULL; if (ADVERTISED_100baseT_Half) tmp |= ADVERTISE_100HALF; if (ADVERTISED_100baseT_Full) tmp |= ADVERTISE_100FULL; if (advert != tmp) { mii->mdio_write(dev, mii->phy_id, MII_ADVERTISE, tmp); mii->advertising = tmp; } /* turn on autonegotiation, and force a renegotiate */ bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART); mii->mdio_write(dev, mii->phy_id, MII_BMCR, bmcr); mii->force_media = 0; } else { u32 bmcr, tmp; /* turn off auto negotiation, set speed and duplexity */ bmcr = mii->mdio_read(dev, mii->phy_id, MII_BMCR); tmp = bmcr & ~(BMCR_ANENABLE | BMCR_SPEED100 | BMCR_FULLDPLX); if (ecmd->speed == SPEED_100) tmp |= BMCR_SPEED100; if (ecmd->duplex == DUPLEX_FULL) { tmp |= BMCR_FULLDPLX; mii->full_duplex = 1; } else mii->full_duplex = 0; if (bmcr != tmp) mii->mdio_write(dev, mii->phy_id, MII_BMCR, tmp); mii->force_media = 1; } return 0; } int _kc_mii_link_ok(struct mii_if_info *mii) { /* first, a dummy read, needed to latch some MII phys */ mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR); if (mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR) & BMSR_LSTATUS) return 1; return 0; } int _kc_mii_nway_restart(struct mii_if_info *mii) { int bmcr; int r = -EINVAL; /* if autoneg is off, it's an error */ bmcr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMCR); if (bmcr & BMCR_ANENABLE) { bmcr |= BMCR_ANRESTART; mii->mdio_write(mii->dev, mii->phy_id, MII_BMCR, bmcr); r = 0; } return r; } void _kc_mii_check_link(struct mii_if_info *mii) { int cur_link = mii_link_ok(mii); int prev_link = netif_carrier_ok(mii->dev); if (cur_link && !prev_link) netif_carrier_on(mii->dev); else if (prev_link && !cur_link) netif_carrier_off(mii->dev); } #if ( LINUX_VERSION_CODE > KERNEL_VERSION(2,4,6) ) int _kc_generic_mii_ioctl(struct mii_if_info *mii_if, struct mii_ioctl_data *mii_data, int cmd, unsigned int *duplex_chg_out) { int rc = 0; unsigned int duplex_changed = 0; if (duplex_chg_out) *duplex_chg_out = 0; mii_data->phy_id &= mii_if->phy_id_mask; mii_data->reg_num &= mii_if->reg_num_mask; switch (cmd) { case SIOCDEVPRIVATE: /* binary compat, remove in 2.5 */ case SIOCGMIIPHY: mii_data->phy_id = mii_if->phy_id; /* fall through */ case SIOCDEVPRIVATE + 1: /* binary compat, remove in 2.5 */ case SIOCGMIIREG: mii_data->val_out = mii_if->mdio_read(mii_if->dev, mii_data->phy_id, mii_data->reg_num); break; case SIOCDEVPRIVATE + 2: /* binary compat, remove in 2.5 */ case SIOCSMIIREG:{ u16 val = mii_data->val_in; if (!capable(CAP_NET_ADMIN)) return -EPERM; if (mii_data->phy_id == mii_if->phy_id) { switch (mii_data->reg_num) { case MII_BMCR:{ unsigned int new_duplex = 0; if (val & (BMCR_RESET | BMCR_ANENABLE)) mii_if->force_media = 0; else mii_if->force_media = 1; if (mii_if->force_media && (val & BMCR_FULLDPLX)) new_duplex = 1; if (mii_if->full_duplex != new_duplex) { duplex_changed = 1; mii_if->full_duplex = new_duplex; } break; } case MII_ADVERTISE: mii_if->advertising = val; break; default: /* do nothing */ break; } } mii_if->mdio_write(mii_if->dev, mii_data->phy_id, mii_data->reg_num, val); break; } default: rc = -EOPNOTSUPP; break; } if ((rc == 0) && (duplex_chg_out) && (duplex_changed)) *duplex_chg_out = 1; return rc; } #endif /* > 2.4.6 */