162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * This file implement the Wireless Extensions priv API.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Authors :	Jean Tourrilhes - HPL - <jt@hpl.hp.com>
562306a36Sopenharmony_ci * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved.
662306a36Sopenharmony_ci * Copyright	2009 Johannes Berg <johannes@sipsolutions.net>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * (As all part of the Linux kernel, this file is GPL)
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/wireless.h>
1262306a36Sopenharmony_ci#include <linux/netdevice.h>
1362306a36Sopenharmony_ci#include <net/iw_handler.h>
1462306a36Sopenharmony_ci#include <net/wext.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ciint iw_handler_get_private(struct net_device *		dev,
1762306a36Sopenharmony_ci			   struct iw_request_info *	info,
1862306a36Sopenharmony_ci			   union iwreq_data *		wrqu,
1962306a36Sopenharmony_ci			   char *			extra)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	/* Check if the driver has something to export */
2262306a36Sopenharmony_ci	if ((dev->wireless_handlers->num_private_args == 0) ||
2362306a36Sopenharmony_ci	   (dev->wireless_handlers->private_args == NULL))
2462306a36Sopenharmony_ci		return -EOPNOTSUPP;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	/* Check if there is enough buffer up there */
2762306a36Sopenharmony_ci	if (wrqu->data.length < dev->wireless_handlers->num_private_args) {
2862306a36Sopenharmony_ci		/* User space can't know in advance how large the buffer
2962306a36Sopenharmony_ci		 * needs to be. Give it a hint, so that we can support
3062306a36Sopenharmony_ci		 * any size buffer we want somewhat efficiently... */
3162306a36Sopenharmony_ci		wrqu->data.length = dev->wireless_handlers->num_private_args;
3262306a36Sopenharmony_ci		return -E2BIG;
3362306a36Sopenharmony_ci	}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	/* Set the number of available ioctls. */
3662306a36Sopenharmony_ci	wrqu->data.length = dev->wireless_handlers->num_private_args;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	/* Copy structure to the user buffer. */
3962306a36Sopenharmony_ci	memcpy(extra, dev->wireless_handlers->private_args,
4062306a36Sopenharmony_ci	       sizeof(struct iw_priv_args) * wrqu->data.length);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	return 0;
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/* Size (in bytes) of the various private data types */
4662306a36Sopenharmony_cistatic const char iw_priv_type_size[] = {
4762306a36Sopenharmony_ci	0,				/* IW_PRIV_TYPE_NONE */
4862306a36Sopenharmony_ci	1,				/* IW_PRIV_TYPE_BYTE */
4962306a36Sopenharmony_ci	1,				/* IW_PRIV_TYPE_CHAR */
5062306a36Sopenharmony_ci	0,				/* Not defined */
5162306a36Sopenharmony_ci	sizeof(__u32),			/* IW_PRIV_TYPE_INT */
5262306a36Sopenharmony_ci	sizeof(struct iw_freq),		/* IW_PRIV_TYPE_FLOAT */
5362306a36Sopenharmony_ci	sizeof(struct sockaddr),	/* IW_PRIV_TYPE_ADDR */
5462306a36Sopenharmony_ci	0,				/* Not defined */
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int get_priv_size(__u16 args)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	int	num = args & IW_PRIV_SIZE_MASK;
6062306a36Sopenharmony_ci	int	type = (args & IW_PRIV_TYPE_MASK) >> 12;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	return num * iw_priv_type_size[type];
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic int adjust_priv_size(__u16 args, struct iw_point *iwp)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	int	num = iwp->length;
6862306a36Sopenharmony_ci	int	max = args & IW_PRIV_SIZE_MASK;
6962306a36Sopenharmony_ci	int	type = (args & IW_PRIV_TYPE_MASK) >> 12;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	/* Make sure the driver doesn't goof up */
7262306a36Sopenharmony_ci	if (max < num)
7362306a36Sopenharmony_ci		num = max;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	return num * iw_priv_type_size[type];
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci/*
7962306a36Sopenharmony_ci * Wrapper to call a private Wireless Extension handler.
8062306a36Sopenharmony_ci * We do various checks and also take care of moving data between
8162306a36Sopenharmony_ci * user space and kernel space.
8262306a36Sopenharmony_ci * It's not as nice and slimline as the standard wrapper. The cause
8362306a36Sopenharmony_ci * is struct iw_priv_args, which was not really designed for the
8462306a36Sopenharmony_ci * job we are going here.
8562306a36Sopenharmony_ci *
8662306a36Sopenharmony_ci * IMPORTANT : This function prevent to set and get data on the same
8762306a36Sopenharmony_ci * IOCTL and enforce the SET/GET convention. Not doing it would be
8862306a36Sopenharmony_ci * far too hairy...
8962306a36Sopenharmony_ci * If you need to set and get data at the same time, please don't use
9062306a36Sopenharmony_ci * a iw_handler but process it in your ioctl handler (i.e. use the
9162306a36Sopenharmony_ci * old driver API).
9262306a36Sopenharmony_ci */
9362306a36Sopenharmony_cistatic int get_priv_descr_and_size(struct net_device *dev, unsigned int cmd,
9462306a36Sopenharmony_ci				   const struct iw_priv_args **descrp)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	const struct iw_priv_args *descr;
9762306a36Sopenharmony_ci	int i, extra_size;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	descr = NULL;
10062306a36Sopenharmony_ci	for (i = 0; i < dev->wireless_handlers->num_private_args; i++) {
10162306a36Sopenharmony_ci		if (cmd == dev->wireless_handlers->private_args[i].cmd) {
10262306a36Sopenharmony_ci			descr = &dev->wireless_handlers->private_args[i];
10362306a36Sopenharmony_ci			break;
10462306a36Sopenharmony_ci		}
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	extra_size = 0;
10862306a36Sopenharmony_ci	if (descr) {
10962306a36Sopenharmony_ci		if (IW_IS_SET(cmd)) {
11062306a36Sopenharmony_ci			int	offset = 0;	/* For sub-ioctls */
11162306a36Sopenharmony_ci			/* Check for sub-ioctl handler */
11262306a36Sopenharmony_ci			if (descr->name[0] == '\0')
11362306a36Sopenharmony_ci				/* Reserve one int for sub-ioctl index */
11462306a36Sopenharmony_ci				offset = sizeof(__u32);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci			/* Size of set arguments */
11762306a36Sopenharmony_ci			extra_size = get_priv_size(descr->set_args);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci			/* Does it fits in iwr ? */
12062306a36Sopenharmony_ci			if ((descr->set_args & IW_PRIV_SIZE_FIXED) &&
12162306a36Sopenharmony_ci			   ((extra_size + offset) <= IFNAMSIZ))
12262306a36Sopenharmony_ci				extra_size = 0;
12362306a36Sopenharmony_ci		} else {
12462306a36Sopenharmony_ci			/* Size of get arguments */
12562306a36Sopenharmony_ci			extra_size = get_priv_size(descr->get_args);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci			/* Does it fits in iwr ? */
12862306a36Sopenharmony_ci			if ((descr->get_args & IW_PRIV_SIZE_FIXED) &&
12962306a36Sopenharmony_ci			   (extra_size <= IFNAMSIZ))
13062306a36Sopenharmony_ci				extra_size = 0;
13162306a36Sopenharmony_ci		}
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci	*descrp = descr;
13462306a36Sopenharmony_ci	return extra_size;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic int ioctl_private_iw_point(struct iw_point *iwp, unsigned int cmd,
13862306a36Sopenharmony_ci				  const struct iw_priv_args *descr,
13962306a36Sopenharmony_ci				  iw_handler handler, struct net_device *dev,
14062306a36Sopenharmony_ci				  struct iw_request_info *info, int extra_size)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	char *extra;
14362306a36Sopenharmony_ci	int err;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	/* Check what user space is giving us */
14662306a36Sopenharmony_ci	if (IW_IS_SET(cmd)) {
14762306a36Sopenharmony_ci		if (!iwp->pointer && iwp->length != 0)
14862306a36Sopenharmony_ci			return -EFAULT;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci		if (iwp->length > (descr->set_args & IW_PRIV_SIZE_MASK))
15162306a36Sopenharmony_ci			return -E2BIG;
15262306a36Sopenharmony_ci	} else if (!iwp->pointer)
15362306a36Sopenharmony_ci		return -EFAULT;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	extra = kzalloc(extra_size, GFP_KERNEL);
15662306a36Sopenharmony_ci	if (!extra)
15762306a36Sopenharmony_ci		return -ENOMEM;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	/* If it is a SET, get all the extra data in here */
16062306a36Sopenharmony_ci	if (IW_IS_SET(cmd) && (iwp->length != 0)) {
16162306a36Sopenharmony_ci		if (copy_from_user(extra, iwp->pointer, extra_size)) {
16262306a36Sopenharmony_ci			err = -EFAULT;
16362306a36Sopenharmony_ci			goto out;
16462306a36Sopenharmony_ci		}
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/* Call the handler */
16862306a36Sopenharmony_ci	err = handler(dev, info, (union iwreq_data *) iwp, extra);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	/* If we have something to return to the user */
17162306a36Sopenharmony_ci	if (!err && IW_IS_GET(cmd)) {
17262306a36Sopenharmony_ci		/* Adjust for the actual length if it's variable,
17362306a36Sopenharmony_ci		 * avoid leaking kernel bits outside.
17462306a36Sopenharmony_ci		 */
17562306a36Sopenharmony_ci		if (!(descr->get_args & IW_PRIV_SIZE_FIXED))
17662306a36Sopenharmony_ci			extra_size = adjust_priv_size(descr->get_args, iwp);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		if (copy_to_user(iwp->pointer, extra, extra_size))
17962306a36Sopenharmony_ci			err =  -EFAULT;
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ciout:
18362306a36Sopenharmony_ci	kfree(extra);
18462306a36Sopenharmony_ci	return err;
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ciint ioctl_private_call(struct net_device *dev, struct iwreq *iwr,
18862306a36Sopenharmony_ci		       unsigned int cmd, struct iw_request_info *info,
18962306a36Sopenharmony_ci		       iw_handler handler)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	int extra_size = 0, ret = -EINVAL;
19262306a36Sopenharmony_ci	const struct iw_priv_args *descr;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	extra_size = get_priv_descr_and_size(dev, cmd, &descr);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	/* Check if we have a pointer to user space data or not. */
19762306a36Sopenharmony_ci	if (extra_size == 0) {
19862306a36Sopenharmony_ci		/* No extra arguments. Trivial to handle */
19962306a36Sopenharmony_ci		ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u));
20062306a36Sopenharmony_ci	} else {
20162306a36Sopenharmony_ci		ret = ioctl_private_iw_point(&iwr->u.data, cmd, descr,
20262306a36Sopenharmony_ci					     handler, dev, info, extra_size);
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	/* Call commit handler if needed and defined */
20662306a36Sopenharmony_ci	if (ret == -EIWCOMMIT)
20762306a36Sopenharmony_ci		ret = call_commit_handler(dev);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	return ret;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci#ifdef CONFIG_COMPAT
21362306a36Sopenharmony_ciint compat_private_call(struct net_device *dev, struct iwreq *iwr,
21462306a36Sopenharmony_ci			unsigned int cmd, struct iw_request_info *info,
21562306a36Sopenharmony_ci			iw_handler handler)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	const struct iw_priv_args *descr;
21862306a36Sopenharmony_ci	int ret, extra_size;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	extra_size = get_priv_descr_and_size(dev, cmd, &descr);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	/* Check if we have a pointer to user space data or not. */
22362306a36Sopenharmony_ci	if (extra_size == 0) {
22462306a36Sopenharmony_ci		/* No extra arguments. Trivial to handle */
22562306a36Sopenharmony_ci		ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u));
22662306a36Sopenharmony_ci	} else {
22762306a36Sopenharmony_ci		struct compat_iw_point *iwp_compat;
22862306a36Sopenharmony_ci		struct iw_point iwp;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci		iwp_compat = (struct compat_iw_point *) &iwr->u.data;
23162306a36Sopenharmony_ci		iwp.pointer = compat_ptr(iwp_compat->pointer);
23262306a36Sopenharmony_ci		iwp.length = iwp_compat->length;
23362306a36Sopenharmony_ci		iwp.flags = iwp_compat->flags;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci		ret = ioctl_private_iw_point(&iwp, cmd, descr,
23662306a36Sopenharmony_ci					     handler, dev, info, extra_size);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci		iwp_compat->pointer = ptr_to_compat(iwp.pointer);
23962306a36Sopenharmony_ci		iwp_compat->length = iwp.length;
24062306a36Sopenharmony_ci		iwp_compat->flags = iwp.flags;
24162306a36Sopenharmony_ci	}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	/* Call commit handler if needed and defined */
24462306a36Sopenharmony_ci	if (ret == -EIWCOMMIT)
24562306a36Sopenharmony_ci		ret = call_commit_handler(dev);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return ret;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci#endif
250