18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * This file implement the Wireless Extensions priv API.
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Authors :	Jean Tourrilhes - HPL - <jt@hpl.hp.com>
58c2ecf20Sopenharmony_ci * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved.
68c2ecf20Sopenharmony_ci * Copyright	2009 Johannes Berg <johannes@sipsolutions.net>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * (As all part of the Linux kernel, this file is GPL)
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/wireless.h>
128c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
138c2ecf20Sopenharmony_ci#include <net/iw_handler.h>
148c2ecf20Sopenharmony_ci#include <net/wext.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ciint iw_handler_get_private(struct net_device *		dev,
178c2ecf20Sopenharmony_ci			   struct iw_request_info *	info,
188c2ecf20Sopenharmony_ci			   union iwreq_data *		wrqu,
198c2ecf20Sopenharmony_ci			   char *			extra)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	/* Check if the driver has something to export */
228c2ecf20Sopenharmony_ci	if ((dev->wireless_handlers->num_private_args == 0) ||
238c2ecf20Sopenharmony_ci	   (dev->wireless_handlers->private_args == NULL))
248c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci	/* Check if there is enough buffer up there */
278c2ecf20Sopenharmony_ci	if (wrqu->data.length < dev->wireless_handlers->num_private_args) {
288c2ecf20Sopenharmony_ci		/* User space can't know in advance how large the buffer
298c2ecf20Sopenharmony_ci		 * needs to be. Give it a hint, so that we can support
308c2ecf20Sopenharmony_ci		 * any size buffer we want somewhat efficiently... */
318c2ecf20Sopenharmony_ci		wrqu->data.length = dev->wireless_handlers->num_private_args;
328c2ecf20Sopenharmony_ci		return -E2BIG;
338c2ecf20Sopenharmony_ci	}
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	/* Set the number of available ioctls. */
368c2ecf20Sopenharmony_ci	wrqu->data.length = dev->wireless_handlers->num_private_args;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	/* Copy structure to the user buffer. */
398c2ecf20Sopenharmony_ci	memcpy(extra, dev->wireless_handlers->private_args,
408c2ecf20Sopenharmony_ci	       sizeof(struct iw_priv_args) * wrqu->data.length);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	return 0;
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* Size (in bytes) of the various private data types */
468c2ecf20Sopenharmony_cistatic const char iw_priv_type_size[] = {
478c2ecf20Sopenharmony_ci	0,				/* IW_PRIV_TYPE_NONE */
488c2ecf20Sopenharmony_ci	1,				/* IW_PRIV_TYPE_BYTE */
498c2ecf20Sopenharmony_ci	1,				/* IW_PRIV_TYPE_CHAR */
508c2ecf20Sopenharmony_ci	0,				/* Not defined */
518c2ecf20Sopenharmony_ci	sizeof(__u32),			/* IW_PRIV_TYPE_INT */
528c2ecf20Sopenharmony_ci	sizeof(struct iw_freq),		/* IW_PRIV_TYPE_FLOAT */
538c2ecf20Sopenharmony_ci	sizeof(struct sockaddr),	/* IW_PRIV_TYPE_ADDR */
548c2ecf20Sopenharmony_ci	0,				/* Not defined */
558c2ecf20Sopenharmony_ci};
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_cistatic int get_priv_size(__u16 args)
588c2ecf20Sopenharmony_ci{
598c2ecf20Sopenharmony_ci	int	num = args & IW_PRIV_SIZE_MASK;
608c2ecf20Sopenharmony_ci	int	type = (args & IW_PRIV_TYPE_MASK) >> 12;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	return num * iw_priv_type_size[type];
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic int adjust_priv_size(__u16 args, struct iw_point *iwp)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	int	num = iwp->length;
688c2ecf20Sopenharmony_ci	int	max = args & IW_PRIV_SIZE_MASK;
698c2ecf20Sopenharmony_ci	int	type = (args & IW_PRIV_TYPE_MASK) >> 12;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	/* Make sure the driver doesn't goof up */
728c2ecf20Sopenharmony_ci	if (max < num)
738c2ecf20Sopenharmony_ci		num = max;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	return num * iw_priv_type_size[type];
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/*
798c2ecf20Sopenharmony_ci * Wrapper to call a private Wireless Extension handler.
808c2ecf20Sopenharmony_ci * We do various checks and also take care of moving data between
818c2ecf20Sopenharmony_ci * user space and kernel space.
828c2ecf20Sopenharmony_ci * It's not as nice and slimline as the standard wrapper. The cause
838c2ecf20Sopenharmony_ci * is struct iw_priv_args, which was not really designed for the
848c2ecf20Sopenharmony_ci * job we are going here.
858c2ecf20Sopenharmony_ci *
868c2ecf20Sopenharmony_ci * IMPORTANT : This function prevent to set and get data on the same
878c2ecf20Sopenharmony_ci * IOCTL and enforce the SET/GET convention. Not doing it would be
888c2ecf20Sopenharmony_ci * far too hairy...
898c2ecf20Sopenharmony_ci * If you need to set and get data at the same time, please don't use
908c2ecf20Sopenharmony_ci * a iw_handler but process it in your ioctl handler (i.e. use the
918c2ecf20Sopenharmony_ci * old driver API).
928c2ecf20Sopenharmony_ci */
938c2ecf20Sopenharmony_cistatic int get_priv_descr_and_size(struct net_device *dev, unsigned int cmd,
948c2ecf20Sopenharmony_ci				   const struct iw_priv_args **descrp)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	const struct iw_priv_args *descr;
978c2ecf20Sopenharmony_ci	int i, extra_size;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	descr = NULL;
1008c2ecf20Sopenharmony_ci	for (i = 0; i < dev->wireless_handlers->num_private_args; i++) {
1018c2ecf20Sopenharmony_ci		if (cmd == dev->wireless_handlers->private_args[i].cmd) {
1028c2ecf20Sopenharmony_ci			descr = &dev->wireless_handlers->private_args[i];
1038c2ecf20Sopenharmony_ci			break;
1048c2ecf20Sopenharmony_ci		}
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	extra_size = 0;
1088c2ecf20Sopenharmony_ci	if (descr) {
1098c2ecf20Sopenharmony_ci		if (IW_IS_SET(cmd)) {
1108c2ecf20Sopenharmony_ci			int	offset = 0;	/* For sub-ioctls */
1118c2ecf20Sopenharmony_ci			/* Check for sub-ioctl handler */
1128c2ecf20Sopenharmony_ci			if (descr->name[0] == '\0')
1138c2ecf20Sopenharmony_ci				/* Reserve one int for sub-ioctl index */
1148c2ecf20Sopenharmony_ci				offset = sizeof(__u32);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci			/* Size of set arguments */
1178c2ecf20Sopenharmony_ci			extra_size = get_priv_size(descr->set_args);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci			/* Does it fits in iwr ? */
1208c2ecf20Sopenharmony_ci			if ((descr->set_args & IW_PRIV_SIZE_FIXED) &&
1218c2ecf20Sopenharmony_ci			   ((extra_size + offset) <= IFNAMSIZ))
1228c2ecf20Sopenharmony_ci				extra_size = 0;
1238c2ecf20Sopenharmony_ci		} else {
1248c2ecf20Sopenharmony_ci			/* Size of get arguments */
1258c2ecf20Sopenharmony_ci			extra_size = get_priv_size(descr->get_args);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci			/* Does it fits in iwr ? */
1288c2ecf20Sopenharmony_ci			if ((descr->get_args & IW_PRIV_SIZE_FIXED) &&
1298c2ecf20Sopenharmony_ci			   (extra_size <= IFNAMSIZ))
1308c2ecf20Sopenharmony_ci				extra_size = 0;
1318c2ecf20Sopenharmony_ci		}
1328c2ecf20Sopenharmony_ci	}
1338c2ecf20Sopenharmony_ci	*descrp = descr;
1348c2ecf20Sopenharmony_ci	return extra_size;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic int ioctl_private_iw_point(struct iw_point *iwp, unsigned int cmd,
1388c2ecf20Sopenharmony_ci				  const struct iw_priv_args *descr,
1398c2ecf20Sopenharmony_ci				  iw_handler handler, struct net_device *dev,
1408c2ecf20Sopenharmony_ci				  struct iw_request_info *info, int extra_size)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	char *extra;
1438c2ecf20Sopenharmony_ci	int err;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	/* Check what user space is giving us */
1468c2ecf20Sopenharmony_ci	if (IW_IS_SET(cmd)) {
1478c2ecf20Sopenharmony_ci		if (!iwp->pointer && iwp->length != 0)
1488c2ecf20Sopenharmony_ci			return -EFAULT;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci		if (iwp->length > (descr->set_args & IW_PRIV_SIZE_MASK))
1518c2ecf20Sopenharmony_ci			return -E2BIG;
1528c2ecf20Sopenharmony_ci	} else if (!iwp->pointer)
1538c2ecf20Sopenharmony_ci		return -EFAULT;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	extra = kzalloc(extra_size, GFP_KERNEL);
1568c2ecf20Sopenharmony_ci	if (!extra)
1578c2ecf20Sopenharmony_ci		return -ENOMEM;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	/* If it is a SET, get all the extra data in here */
1608c2ecf20Sopenharmony_ci	if (IW_IS_SET(cmd) && (iwp->length != 0)) {
1618c2ecf20Sopenharmony_ci		if (copy_from_user(extra, iwp->pointer, extra_size)) {
1628c2ecf20Sopenharmony_ci			err = -EFAULT;
1638c2ecf20Sopenharmony_ci			goto out;
1648c2ecf20Sopenharmony_ci		}
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	/* Call the handler */
1688c2ecf20Sopenharmony_ci	err = handler(dev, info, (union iwreq_data *) iwp, extra);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	/* If we have something to return to the user */
1718c2ecf20Sopenharmony_ci	if (!err && IW_IS_GET(cmd)) {
1728c2ecf20Sopenharmony_ci		/* Adjust for the actual length if it's variable,
1738c2ecf20Sopenharmony_ci		 * avoid leaking kernel bits outside.
1748c2ecf20Sopenharmony_ci		 */
1758c2ecf20Sopenharmony_ci		if (!(descr->get_args & IW_PRIV_SIZE_FIXED))
1768c2ecf20Sopenharmony_ci			extra_size = adjust_priv_size(descr->get_args, iwp);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci		if (copy_to_user(iwp->pointer, extra, extra_size))
1798c2ecf20Sopenharmony_ci			err =  -EFAULT;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ciout:
1838c2ecf20Sopenharmony_ci	kfree(extra);
1848c2ecf20Sopenharmony_ci	return err;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ciint ioctl_private_call(struct net_device *dev, struct iwreq *iwr,
1888c2ecf20Sopenharmony_ci		       unsigned int cmd, struct iw_request_info *info,
1898c2ecf20Sopenharmony_ci		       iw_handler handler)
1908c2ecf20Sopenharmony_ci{
1918c2ecf20Sopenharmony_ci	int extra_size = 0, ret = -EINVAL;
1928c2ecf20Sopenharmony_ci	const struct iw_priv_args *descr;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	extra_size = get_priv_descr_and_size(dev, cmd, &descr);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	/* Check if we have a pointer to user space data or not. */
1978c2ecf20Sopenharmony_ci	if (extra_size == 0) {
1988c2ecf20Sopenharmony_ci		/* No extra arguments. Trivial to handle */
1998c2ecf20Sopenharmony_ci		ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u));
2008c2ecf20Sopenharmony_ci	} else {
2018c2ecf20Sopenharmony_ci		ret = ioctl_private_iw_point(&iwr->u.data, cmd, descr,
2028c2ecf20Sopenharmony_ci					     handler, dev, info, extra_size);
2038c2ecf20Sopenharmony_ci	}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	/* Call commit handler if needed and defined */
2068c2ecf20Sopenharmony_ci	if (ret == -EIWCOMMIT)
2078c2ecf20Sopenharmony_ci		ret = call_commit_handler(dev);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	return ret;
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT
2138c2ecf20Sopenharmony_ciint compat_private_call(struct net_device *dev, struct iwreq *iwr,
2148c2ecf20Sopenharmony_ci			unsigned int cmd, struct iw_request_info *info,
2158c2ecf20Sopenharmony_ci			iw_handler handler)
2168c2ecf20Sopenharmony_ci{
2178c2ecf20Sopenharmony_ci	const struct iw_priv_args *descr;
2188c2ecf20Sopenharmony_ci	int ret, extra_size;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	extra_size = get_priv_descr_and_size(dev, cmd, &descr);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	/* Check if we have a pointer to user space data or not. */
2238c2ecf20Sopenharmony_ci	if (extra_size == 0) {
2248c2ecf20Sopenharmony_ci		/* No extra arguments. Trivial to handle */
2258c2ecf20Sopenharmony_ci		ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u));
2268c2ecf20Sopenharmony_ci	} else {
2278c2ecf20Sopenharmony_ci		struct compat_iw_point *iwp_compat;
2288c2ecf20Sopenharmony_ci		struct iw_point iwp;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci		iwp_compat = (struct compat_iw_point *) &iwr->u.data;
2318c2ecf20Sopenharmony_ci		iwp.pointer = compat_ptr(iwp_compat->pointer);
2328c2ecf20Sopenharmony_ci		iwp.length = iwp_compat->length;
2338c2ecf20Sopenharmony_ci		iwp.flags = iwp_compat->flags;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci		ret = ioctl_private_iw_point(&iwp, cmd, descr,
2368c2ecf20Sopenharmony_ci					     handler, dev, info, extra_size);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci		iwp_compat->pointer = ptr_to_compat(iwp.pointer);
2398c2ecf20Sopenharmony_ci		iwp_compat->length = iwp.length;
2408c2ecf20Sopenharmony_ci		iwp_compat->flags = iwp.flags;
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	/* Call commit handler if needed and defined */
2448c2ecf20Sopenharmony_ci	if (ret == -EIWCOMMIT)
2458c2ecf20Sopenharmony_ci		ret = call_commit_handler(dev);
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	return ret;
2488c2ecf20Sopenharmony_ci}
2498c2ecf20Sopenharmony_ci#endif
250