874 lines · 20.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0#include <linux/kmod.h>#include <linux/netdevice.h>#include <linux/inetdevice.h>#include <linux/etherdevice.h>#include <linux/rtnetlink.h>#include <linux/net_tstamp.h>#include <linux/phylib_stubs.h>#include <linux/ptp_clock_kernel.h>#include <linux/wireless.h>#include <linux/if_bridge.h>#include <net/dsa_stubs.h>#include <net/netdev_lock.h>#include <net/wext.h> #include "dev.h" /* *	Map an interface index to its name (SIOCGIFNAME) */ /* *	We need this ioctl for efficient implementation of the *	if_indextoname() function required by the IPv6 API.  Without *	it, we would have to search all the interfaces to find a *	match.  --pb */ static int dev_ifname(struct net *net, struct ifreq *ifr){	ifr->ifr_name[IFNAMSIZ-1] = 0;	return netdev_get_name(net, ifr->ifr_name, ifr->ifr_ifindex);} /* *	Perform a SIOCGIFCONF call. This structure will change *	size eventually, and there is nothing I can do about it. *	Thus we will need a 'compatibility mode'. */int dev_ifconf(struct net *net, struct ifconf __user *uifc){	struct net_device *dev;	void __user *pos;	size_t size;	int len, total = 0, done; 	/* both the ifconf and the ifreq structures are slightly different */	if (in_compat_syscall()) {		struct compat_ifconf ifc32; 		if (copy_from_user(&ifc32, uifc, sizeof(struct compat_ifconf)))			return -EFAULT; 		pos = compat_ptr(ifc32.ifcbuf);		len = ifc32.ifc_len;		size = sizeof(struct compat_ifreq);	} else {		struct ifconf ifc; 		if (copy_from_user(&ifc, uifc, sizeof(struct ifconf)))			return -EFAULT; 		pos = ifc.ifc_buf;		len = ifc.ifc_len;		size = sizeof(struct ifreq);	} 	/* Loop over the interfaces, and write an info block for each. */	rtnl_net_lock(net);	for_each_netdev(net, dev) {		if (!pos)			done = inet_gifconf(dev, NULL, 0, size);		else			done = inet_gifconf(dev, pos + total,					    len - total, size);		if (done < 0) {			rtnl_net_unlock(net);			return -EFAULT;		}		total += done;	}	rtnl_net_unlock(net); 	return put_user(total, &uifc->ifc_len);} static int dev_getifmap(struct net_device *dev, struct ifreq *ifr){	struct ifmap *ifmap = &ifr->ifr_map; 	if (in_compat_syscall()) {		struct compat_ifmap *cifmap = (struct compat_ifmap *)ifmap; 		cifmap->mem_start = dev->mem_start;		cifmap->mem_end   = dev->mem_end;		cifmap->base_addr = dev->base_addr;		cifmap->irq       = dev->irq;		cifmap->dma       = dev->dma;		cifmap->port      = dev->if_port; 		return 0;	} 	ifmap->mem_start  = dev->mem_start;	ifmap->mem_end    = dev->mem_end;	ifmap->base_addr  = dev->base_addr;	ifmap->irq        = dev->irq;	ifmap->dma        = dev->dma;	ifmap->port       = dev->if_port; 	return 0;} static int netif_setifmap(struct net_device *dev, struct ifreq *ifr){	struct compat_ifmap *cifmap = (struct compat_ifmap *)&ifr->ifr_map; 	if (!dev->netdev_ops->ndo_set_config)		return -EOPNOTSUPP; 	if (in_compat_syscall()) {		struct ifmap ifmap = {			.mem_start  = cifmap->mem_start,			.mem_end    = cifmap->mem_end,			.base_addr  = cifmap->base_addr,			.irq        = cifmap->irq,			.dma        = cifmap->dma,			.port       = cifmap->port,		}; 		return dev->netdev_ops->ndo_set_config(dev, &ifmap);	} 	return dev->netdev_ops->ndo_set_config(dev, &ifr->ifr_map);} /* *	Perform the SIOCxIFxxx calls, inside rcu_read_lock() */static int dev_ifsioc_locked(struct net *net, struct ifreq *ifr, unsigned int cmd){	int err;	struct net_device *dev = dev_get_by_name_rcu(net, ifr->ifr_name); 	if (!dev)		return -ENODEV; 	switch (cmd) {	case SIOCGIFFLAGS:	/* Get interface flags */		ifr->ifr_flags = (short)netif_get_flags(dev);		return 0; 	case SIOCGIFMETRIC:	/* Get the metric on the interface				   (currently unused) */		ifr->ifr_metric = 0;		return 0; 	case SIOCGIFMTU:	/* Get the MTU of a device */		ifr->ifr_mtu = dev->mtu;		return 0; 	case SIOCGIFSLAVE:		err = -EINVAL;		break; 	case SIOCGIFMAP:		return dev_getifmap(dev, ifr); 	case SIOCGIFINDEX:		ifr->ifr_ifindex = dev->ifindex;		return 0; 	case SIOCGIFTXQLEN:		ifr->ifr_qlen = dev->tx_queue_len;		return 0; 	default:		/* dev_ioctl() should ensure this case		 * is never reached		 */		WARN_ON(1);		err = -ENOTTY;		break; 	}	return err;} int net_hwtstamp_validate(const struct kernel_hwtstamp_config *cfg){	enum hwtstamp_tx_types tx_type;	enum hwtstamp_rx_filters rx_filter;	int tx_type_valid = 0;	int rx_filter_valid = 0; 	if (cfg->flags & ~HWTSTAMP_FLAG_MASK)		return -EINVAL; 	tx_type = cfg->tx_type;	rx_filter = cfg->rx_filter; 	switch (tx_type) {	case HWTSTAMP_TX_OFF:	case HWTSTAMP_TX_ON:	case HWTSTAMP_TX_ONESTEP_SYNC:	case HWTSTAMP_TX_ONESTEP_P2P:		tx_type_valid = 1;		break;	case __HWTSTAMP_TX_CNT:		/* not a real value */		break;	} 	switch (rx_filter) {	case HWTSTAMP_FILTER_NONE:	case HWTSTAMP_FILTER_ALL:	case HWTSTAMP_FILTER_SOME:	case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:	case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:	case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:	case HWTSTAMP_FILTER_PTP_V2_EVENT:	case HWTSTAMP_FILTER_PTP_V2_SYNC:	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:	case HWTSTAMP_FILTER_NTP_ALL:		rx_filter_valid = 1;		break;	case __HWTSTAMP_FILTER_CNT:		/* not a real value */		break;	} 	if (!tx_type_valid || !rx_filter_valid)		return -ERANGE; 	return 0;} /** * dev_get_hwtstamp_phylib() - Get hardware timestamping settings of NIC *	or of attached phylib PHY * @dev: Network device * @cfg: Timestamping configuration structure * * Helper for calling the default hardware provider timestamping. * * Note: phy_mii_ioctl() only handles SIOCSHWTSTAMP (not SIOCGHWTSTAMP), and * there only exists a phydev->mii_ts->hwtstamp() method. So this will return * -EOPNOTSUPP for phylib for now, which is still more accurate than letting * the netdev handle the GET request. */int dev_get_hwtstamp_phylib(struct net_device *dev,			    struct kernel_hwtstamp_config *cfg){	struct hwtstamp_provider *hwprov; 	hwprov = rtnl_dereference(dev->hwprov);	if (hwprov) {		cfg->qualifier = hwprov->desc.qualifier;		if (hwprov->source == HWTSTAMP_SOURCE_PHYLIB &&		    hwprov->phydev)			return phy_hwtstamp_get(hwprov->phydev, cfg); 		if (hwprov->source == HWTSTAMP_SOURCE_NETDEV)			return dev->netdev_ops->ndo_hwtstamp_get(dev, cfg); 		return -EOPNOTSUPP;	} 	if (phy_is_default_hwtstamp(dev->phydev))		return phy_hwtstamp_get(dev->phydev, cfg); 	return dev->netdev_ops->ndo_hwtstamp_get(dev, cfg);} static int dev_get_hwtstamp(struct net_device *dev, struct ifreq *ifr){	const struct net_device_ops *ops = dev->netdev_ops;	struct kernel_hwtstamp_config kernel_cfg = {};	struct hwtstamp_config cfg;	int err; 	if (!ops->ndo_hwtstamp_get)		return dev_eth_ioctl(dev, ifr, SIOCGHWTSTAMP); /* legacy */ 	if (!netif_device_present(dev))		return -ENODEV; 	kernel_cfg.ifr = ifr;	netdev_lock_ops(dev);	err = dev_get_hwtstamp_phylib(dev, &kernel_cfg);	netdev_unlock_ops(dev);	if (err)		return err; 	/* If the request was resolved through an unconverted driver, omit	 * the copy_to_user(), since the implementation has already done that	 */	if (!kernel_cfg.copied_to_user) {		hwtstamp_config_from_kernel(&cfg, &kernel_cfg); 		if (copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)))			return -EFAULT;	} 	return 0;} /** * dev_set_hwtstamp_phylib() - Change hardware timestamping of NIC *	or of attached phylib PHY * @dev: Network device * @cfg: Timestamping configuration structure * @extack: Netlink extended ack message structure, for error reporting * * Helper for enforcing a common policy that phylib timestamping, if available, * should take precedence in front of hardware timestamping provided by the * netdev. If the netdev driver needs to perform specific actions even for PHY * timestamping to work properly (a switch port must trap the timestamped * frames and not forward them), it must set dev->see_all_hwtstamp_requests. */int dev_set_hwtstamp_phylib(struct net_device *dev,			    struct kernel_hwtstamp_config *cfg,			    struct netlink_ext_ack *extack){	const struct net_device_ops *ops = dev->netdev_ops;	struct kernel_hwtstamp_config old_cfg = {};	struct hwtstamp_provider *hwprov;	struct phy_device *phydev;	bool changed = false;	bool phy_ts;	int err; 	hwprov = rtnl_dereference(dev->hwprov);	if (hwprov) {		if (hwprov->source == HWTSTAMP_SOURCE_PHYLIB &&		    hwprov->phydev) {			phy_ts = true;			phydev = hwprov->phydev;		} else if (hwprov->source == HWTSTAMP_SOURCE_NETDEV) {			phy_ts = false;		} else {			return -EOPNOTSUPP;		} 		cfg->qualifier = hwprov->desc.qualifier;	} else {		phy_ts = phy_is_default_hwtstamp(dev->phydev);		if (phy_ts)			phydev = dev->phydev;	} 	cfg->source = phy_ts ? HWTSTAMP_SOURCE_PHYLIB : HWTSTAMP_SOURCE_NETDEV; 	if (phy_ts && dev->see_all_hwtstamp_requests) {		err = ops->ndo_hwtstamp_get(dev, &old_cfg);		if (err)			return err;	} 	if (!phy_ts || dev->see_all_hwtstamp_requests) {		err = ops->ndo_hwtstamp_set(dev, cfg, extack);		if (err) {			if (extack->_msg)				netdev_err(dev, "%s\n", extack->_msg);			return err;		}	} 	if (phy_ts && dev->see_all_hwtstamp_requests)		changed = kernel_hwtstamp_config_changed(&old_cfg, cfg); 	if (phy_ts) {		err = phy_hwtstamp_set(phydev, cfg, extack);		if (err) {			if (changed)				ops->ndo_hwtstamp_set(dev, &old_cfg, NULL);			return err;		}	} 	return 0;} static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr){	const struct net_device_ops *ops = dev->netdev_ops;	struct kernel_hwtstamp_config kernel_cfg = {};	struct netlink_ext_ack extack = {};	struct hwtstamp_config cfg;	int err; 	if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))		return -EFAULT; 	hwtstamp_config_to_kernel(&kernel_cfg, &cfg);	kernel_cfg.ifr = ifr; 	err = net_hwtstamp_validate(&kernel_cfg);	if (err)		return err; 	err = dsa_conduit_hwtstamp_validate(dev, &kernel_cfg, &extack);	if (err) {		if (extack._msg)			netdev_err(dev, "%s\n", extack._msg);		return err;	} 	if (!ops->ndo_hwtstamp_set)		return dev_eth_ioctl(dev, ifr, SIOCSHWTSTAMP); /* legacy */ 	if (!netif_device_present(dev))		return -ENODEV; 	netdev_lock_ops(dev);	err = dev_set_hwtstamp_phylib(dev, &kernel_cfg, &extack);	netdev_unlock_ops(dev);	if (err)		return err; 	/* The driver may have modified the configuration, so copy the	 * updated version of it back to user space	 */	if (!kernel_cfg.copied_to_user) {		hwtstamp_config_from_kernel(&cfg, &kernel_cfg); 		if (copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)))			return -EFAULT;	} 	return 0;} static int generic_hwtstamp_ioctl_lower(struct net_device *dev, int cmd,					struct kernel_hwtstamp_config *kernel_cfg){	struct ifreq ifrr;	int err; 	if (!kernel_cfg->ifr)		return -EINVAL; 	strscpy_pad(ifrr.ifr_name, dev->name, IFNAMSIZ);	ifrr.ifr_ifru = kernel_cfg->ifr->ifr_ifru; 	err = dev_eth_ioctl(dev, &ifrr, cmd);	if (err)		return err; 	kernel_cfg->ifr->ifr_ifru = ifrr.ifr_ifru;	kernel_cfg->copied_to_user = true; 	return 0;} int generic_hwtstamp_get_lower(struct net_device *dev,			       struct kernel_hwtstamp_config *kernel_cfg){	const struct net_device_ops *ops = dev->netdev_ops; 	if (!netif_device_present(dev))		return -ENODEV; 	if (ops->ndo_hwtstamp_get) {		int err; 		netdev_lock_ops(dev);		err = dev_get_hwtstamp_phylib(dev, kernel_cfg);		netdev_unlock_ops(dev); 		return err;	} 	/* Legacy path: unconverted lower driver */	return generic_hwtstamp_ioctl_lower(dev, SIOCGHWTSTAMP, kernel_cfg);}EXPORT_SYMBOL(generic_hwtstamp_get_lower); int generic_hwtstamp_set_lower(struct net_device *dev,			       struct kernel_hwtstamp_config *kernel_cfg,			       struct netlink_ext_ack *extack){	const struct net_device_ops *ops = dev->netdev_ops; 	if (!netif_device_present(dev))		return -ENODEV; 	if (ops->ndo_hwtstamp_set) {		int err; 		netdev_lock_ops(dev);		err = dev_set_hwtstamp_phylib(dev, kernel_cfg, extack);		netdev_unlock_ops(dev); 

Lines 1–500 of 874.