xref: /OK3568_Linux_fs/kernel/net/wireless/wext-priv.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun /*
2*4882a593Smuzhiyun  * This file implement the Wireless Extensions priv API.
3*4882a593Smuzhiyun  *
4*4882a593Smuzhiyun  * Authors :	Jean Tourrilhes - HPL - <jt@hpl.hp.com>
5*4882a593Smuzhiyun  * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved.
6*4882a593Smuzhiyun  * Copyright	2009 Johannes Berg <johannes@sipsolutions.net>
7*4882a593Smuzhiyun  *
8*4882a593Smuzhiyun  * (As all part of the Linux kernel, this file is GPL)
9*4882a593Smuzhiyun  */
10*4882a593Smuzhiyun #include <linux/slab.h>
11*4882a593Smuzhiyun #include <linux/wireless.h>
12*4882a593Smuzhiyun #include <linux/netdevice.h>
13*4882a593Smuzhiyun #include <net/iw_handler.h>
14*4882a593Smuzhiyun #include <net/wext.h>
15*4882a593Smuzhiyun 
iw_handler_get_private(struct net_device * dev,struct iw_request_info * info,union iwreq_data * wrqu,char * extra)16*4882a593Smuzhiyun int iw_handler_get_private(struct net_device *		dev,
17*4882a593Smuzhiyun 			   struct iw_request_info *	info,
18*4882a593Smuzhiyun 			   union iwreq_data *		wrqu,
19*4882a593Smuzhiyun 			   char *			extra)
20*4882a593Smuzhiyun {
21*4882a593Smuzhiyun 	/* Check if the driver has something to export */
22*4882a593Smuzhiyun 	if ((dev->wireless_handlers->num_private_args == 0) ||
23*4882a593Smuzhiyun 	   (dev->wireless_handlers->private_args == NULL))
24*4882a593Smuzhiyun 		return -EOPNOTSUPP;
25*4882a593Smuzhiyun 
26*4882a593Smuzhiyun 	/* Check if there is enough buffer up there */
27*4882a593Smuzhiyun 	if (wrqu->data.length < dev->wireless_handlers->num_private_args) {
28*4882a593Smuzhiyun 		/* User space can't know in advance how large the buffer
29*4882a593Smuzhiyun 		 * needs to be. Give it a hint, so that we can support
30*4882a593Smuzhiyun 		 * any size buffer we want somewhat efficiently... */
31*4882a593Smuzhiyun 		wrqu->data.length = dev->wireless_handlers->num_private_args;
32*4882a593Smuzhiyun 		return -E2BIG;
33*4882a593Smuzhiyun 	}
34*4882a593Smuzhiyun 
35*4882a593Smuzhiyun 	/* Set the number of available ioctls. */
36*4882a593Smuzhiyun 	wrqu->data.length = dev->wireless_handlers->num_private_args;
37*4882a593Smuzhiyun 
38*4882a593Smuzhiyun 	/* Copy structure to the user buffer. */
39*4882a593Smuzhiyun 	memcpy(extra, dev->wireless_handlers->private_args,
40*4882a593Smuzhiyun 	       sizeof(struct iw_priv_args) * wrqu->data.length);
41*4882a593Smuzhiyun 
42*4882a593Smuzhiyun 	return 0;
43*4882a593Smuzhiyun }
44*4882a593Smuzhiyun 
45*4882a593Smuzhiyun /* Size (in bytes) of the various private data types */
46*4882a593Smuzhiyun static const char iw_priv_type_size[] = {
47*4882a593Smuzhiyun 	0,				/* IW_PRIV_TYPE_NONE */
48*4882a593Smuzhiyun 	1,				/* IW_PRIV_TYPE_BYTE */
49*4882a593Smuzhiyun 	1,				/* IW_PRIV_TYPE_CHAR */
50*4882a593Smuzhiyun 	0,				/* Not defined */
51*4882a593Smuzhiyun 	sizeof(__u32),			/* IW_PRIV_TYPE_INT */
52*4882a593Smuzhiyun 	sizeof(struct iw_freq),		/* IW_PRIV_TYPE_FLOAT */
53*4882a593Smuzhiyun 	sizeof(struct sockaddr),	/* IW_PRIV_TYPE_ADDR */
54*4882a593Smuzhiyun 	0,				/* Not defined */
55*4882a593Smuzhiyun };
56*4882a593Smuzhiyun 
get_priv_size(__u16 args)57*4882a593Smuzhiyun static int get_priv_size(__u16 args)
58*4882a593Smuzhiyun {
59*4882a593Smuzhiyun 	int	num = args & IW_PRIV_SIZE_MASK;
60*4882a593Smuzhiyun 	int	type = (args & IW_PRIV_TYPE_MASK) >> 12;
61*4882a593Smuzhiyun 
62*4882a593Smuzhiyun 	return num * iw_priv_type_size[type];
63*4882a593Smuzhiyun }
64*4882a593Smuzhiyun 
adjust_priv_size(__u16 args,struct iw_point * iwp)65*4882a593Smuzhiyun static int adjust_priv_size(__u16 args, struct iw_point *iwp)
66*4882a593Smuzhiyun {
67*4882a593Smuzhiyun 	int	num = iwp->length;
68*4882a593Smuzhiyun 	int	max = args & IW_PRIV_SIZE_MASK;
69*4882a593Smuzhiyun 	int	type = (args & IW_PRIV_TYPE_MASK) >> 12;
70*4882a593Smuzhiyun 
71*4882a593Smuzhiyun 	/* Make sure the driver doesn't goof up */
72*4882a593Smuzhiyun 	if (max < num)
73*4882a593Smuzhiyun 		num = max;
74*4882a593Smuzhiyun 
75*4882a593Smuzhiyun 	return num * iw_priv_type_size[type];
76*4882a593Smuzhiyun }
77*4882a593Smuzhiyun 
78*4882a593Smuzhiyun /*
79*4882a593Smuzhiyun  * Wrapper to call a private Wireless Extension handler.
80*4882a593Smuzhiyun  * We do various checks and also take care of moving data between
81*4882a593Smuzhiyun  * user space and kernel space.
82*4882a593Smuzhiyun  * It's not as nice and slimline as the standard wrapper. The cause
83*4882a593Smuzhiyun  * is struct iw_priv_args, which was not really designed for the
84*4882a593Smuzhiyun  * job we are going here.
85*4882a593Smuzhiyun  *
86*4882a593Smuzhiyun  * IMPORTANT : This function prevent to set and get data on the same
87*4882a593Smuzhiyun  * IOCTL and enforce the SET/GET convention. Not doing it would be
88*4882a593Smuzhiyun  * far too hairy...
89*4882a593Smuzhiyun  * If you need to set and get data at the same time, please don't use
90*4882a593Smuzhiyun  * a iw_handler but process it in your ioctl handler (i.e. use the
91*4882a593Smuzhiyun  * old driver API).
92*4882a593Smuzhiyun  */
get_priv_descr_and_size(struct net_device * dev,unsigned int cmd,const struct iw_priv_args ** descrp)93*4882a593Smuzhiyun static int get_priv_descr_and_size(struct net_device *dev, unsigned int cmd,
94*4882a593Smuzhiyun 				   const struct iw_priv_args **descrp)
95*4882a593Smuzhiyun {
96*4882a593Smuzhiyun 	const struct iw_priv_args *descr;
97*4882a593Smuzhiyun 	int i, extra_size;
98*4882a593Smuzhiyun 
99*4882a593Smuzhiyun 	descr = NULL;
100*4882a593Smuzhiyun 	for (i = 0; i < dev->wireless_handlers->num_private_args; i++) {
101*4882a593Smuzhiyun 		if (cmd == dev->wireless_handlers->private_args[i].cmd) {
102*4882a593Smuzhiyun 			descr = &dev->wireless_handlers->private_args[i];
103*4882a593Smuzhiyun 			break;
104*4882a593Smuzhiyun 		}
105*4882a593Smuzhiyun 	}
106*4882a593Smuzhiyun 
107*4882a593Smuzhiyun 	extra_size = 0;
108*4882a593Smuzhiyun 	if (descr) {
109*4882a593Smuzhiyun 		if (IW_IS_SET(cmd)) {
110*4882a593Smuzhiyun 			int	offset = 0;	/* For sub-ioctls */
111*4882a593Smuzhiyun 			/* Check for sub-ioctl handler */
112*4882a593Smuzhiyun 			if (descr->name[0] == '\0')
113*4882a593Smuzhiyun 				/* Reserve one int for sub-ioctl index */
114*4882a593Smuzhiyun 				offset = sizeof(__u32);
115*4882a593Smuzhiyun 
116*4882a593Smuzhiyun 			/* Size of set arguments */
117*4882a593Smuzhiyun 			extra_size = get_priv_size(descr->set_args);
118*4882a593Smuzhiyun 
119*4882a593Smuzhiyun 			/* Does it fits in iwr ? */
120*4882a593Smuzhiyun 			if ((descr->set_args & IW_PRIV_SIZE_FIXED) &&
121*4882a593Smuzhiyun 			   ((extra_size + offset) <= IFNAMSIZ))
122*4882a593Smuzhiyun 				extra_size = 0;
123*4882a593Smuzhiyun 		} else {
124*4882a593Smuzhiyun 			/* Size of get arguments */
125*4882a593Smuzhiyun 			extra_size = get_priv_size(descr->get_args);
126*4882a593Smuzhiyun 
127*4882a593Smuzhiyun 			/* Does it fits in iwr ? */
128*4882a593Smuzhiyun 			if ((descr->get_args & IW_PRIV_SIZE_FIXED) &&
129*4882a593Smuzhiyun 			   (extra_size <= IFNAMSIZ))
130*4882a593Smuzhiyun 				extra_size = 0;
131*4882a593Smuzhiyun 		}
132*4882a593Smuzhiyun 	}
133*4882a593Smuzhiyun 	*descrp = descr;
134*4882a593Smuzhiyun 	return extra_size;
135*4882a593Smuzhiyun }
136*4882a593Smuzhiyun 
ioctl_private_iw_point(struct iw_point * iwp,unsigned int cmd,const struct iw_priv_args * descr,iw_handler handler,struct net_device * dev,struct iw_request_info * info,int extra_size)137*4882a593Smuzhiyun static int ioctl_private_iw_point(struct iw_point *iwp, unsigned int cmd,
138*4882a593Smuzhiyun 				  const struct iw_priv_args *descr,
139*4882a593Smuzhiyun 				  iw_handler handler, struct net_device *dev,
140*4882a593Smuzhiyun 				  struct iw_request_info *info, int extra_size)
141*4882a593Smuzhiyun {
142*4882a593Smuzhiyun 	char *extra;
143*4882a593Smuzhiyun 	int err;
144*4882a593Smuzhiyun 
145*4882a593Smuzhiyun 	/* Check what user space is giving us */
146*4882a593Smuzhiyun 	if (IW_IS_SET(cmd)) {
147*4882a593Smuzhiyun 		if (!iwp->pointer && iwp->length != 0)
148*4882a593Smuzhiyun 			return -EFAULT;
149*4882a593Smuzhiyun 
150*4882a593Smuzhiyun 		if (iwp->length > (descr->set_args & IW_PRIV_SIZE_MASK))
151*4882a593Smuzhiyun 			return -E2BIG;
152*4882a593Smuzhiyun 	} else if (!iwp->pointer)
153*4882a593Smuzhiyun 		return -EFAULT;
154*4882a593Smuzhiyun 
155*4882a593Smuzhiyun 	extra = kzalloc(extra_size, GFP_KERNEL);
156*4882a593Smuzhiyun 	if (!extra)
157*4882a593Smuzhiyun 		return -ENOMEM;
158*4882a593Smuzhiyun 
159*4882a593Smuzhiyun 	/* If it is a SET, get all the extra data in here */
160*4882a593Smuzhiyun 	if (IW_IS_SET(cmd) && (iwp->length != 0)) {
161*4882a593Smuzhiyun 		if (copy_from_user(extra, iwp->pointer, extra_size)) {
162*4882a593Smuzhiyun 			err = -EFAULT;
163*4882a593Smuzhiyun 			goto out;
164*4882a593Smuzhiyun 		}
165*4882a593Smuzhiyun 	}
166*4882a593Smuzhiyun 
167*4882a593Smuzhiyun 	/* Call the handler */
168*4882a593Smuzhiyun 	err = handler(dev, info, (union iwreq_data *) iwp, extra);
169*4882a593Smuzhiyun 
170*4882a593Smuzhiyun 	/* If we have something to return to the user */
171*4882a593Smuzhiyun 	if (!err && IW_IS_GET(cmd)) {
172*4882a593Smuzhiyun 		/* Adjust for the actual length if it's variable,
173*4882a593Smuzhiyun 		 * avoid leaking kernel bits outside.
174*4882a593Smuzhiyun 		 */
175*4882a593Smuzhiyun 		if (!(descr->get_args & IW_PRIV_SIZE_FIXED))
176*4882a593Smuzhiyun 			extra_size = adjust_priv_size(descr->get_args, iwp);
177*4882a593Smuzhiyun 
178*4882a593Smuzhiyun 		if (copy_to_user(iwp->pointer, extra, extra_size))
179*4882a593Smuzhiyun 			err =  -EFAULT;
180*4882a593Smuzhiyun 	}
181*4882a593Smuzhiyun 
182*4882a593Smuzhiyun out:
183*4882a593Smuzhiyun 	kfree(extra);
184*4882a593Smuzhiyun 	return err;
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun 
ioctl_private_call(struct net_device * dev,struct iwreq * iwr,unsigned int cmd,struct iw_request_info * info,iw_handler handler)187*4882a593Smuzhiyun int ioctl_private_call(struct net_device *dev, struct iwreq *iwr,
188*4882a593Smuzhiyun 		       unsigned int cmd, struct iw_request_info *info,
189*4882a593Smuzhiyun 		       iw_handler handler)
190*4882a593Smuzhiyun {
191*4882a593Smuzhiyun 	int extra_size = 0, ret = -EINVAL;
192*4882a593Smuzhiyun 	const struct iw_priv_args *descr;
193*4882a593Smuzhiyun 
194*4882a593Smuzhiyun 	extra_size = get_priv_descr_and_size(dev, cmd, &descr);
195*4882a593Smuzhiyun 
196*4882a593Smuzhiyun 	/* Check if we have a pointer to user space data or not. */
197*4882a593Smuzhiyun 	if (extra_size == 0) {
198*4882a593Smuzhiyun 		/* No extra arguments. Trivial to handle */
199*4882a593Smuzhiyun 		ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u));
200*4882a593Smuzhiyun 	} else {
201*4882a593Smuzhiyun 		ret = ioctl_private_iw_point(&iwr->u.data, cmd, descr,
202*4882a593Smuzhiyun 					     handler, dev, info, extra_size);
203*4882a593Smuzhiyun 	}
204*4882a593Smuzhiyun 
205*4882a593Smuzhiyun 	/* Call commit handler if needed and defined */
206*4882a593Smuzhiyun 	if (ret == -EIWCOMMIT)
207*4882a593Smuzhiyun 		ret = call_commit_handler(dev);
208*4882a593Smuzhiyun 
209*4882a593Smuzhiyun 	return ret;
210*4882a593Smuzhiyun }
211*4882a593Smuzhiyun 
212*4882a593Smuzhiyun #ifdef CONFIG_COMPAT
compat_private_call(struct net_device * dev,struct iwreq * iwr,unsigned int cmd,struct iw_request_info * info,iw_handler handler)213*4882a593Smuzhiyun int compat_private_call(struct net_device *dev, struct iwreq *iwr,
214*4882a593Smuzhiyun 			unsigned int cmd, struct iw_request_info *info,
215*4882a593Smuzhiyun 			iw_handler handler)
216*4882a593Smuzhiyun {
217*4882a593Smuzhiyun 	const struct iw_priv_args *descr;
218*4882a593Smuzhiyun 	int ret, extra_size;
219*4882a593Smuzhiyun 
220*4882a593Smuzhiyun 	extra_size = get_priv_descr_and_size(dev, cmd, &descr);
221*4882a593Smuzhiyun 
222*4882a593Smuzhiyun 	/* Check if we have a pointer to user space data or not. */
223*4882a593Smuzhiyun 	if (extra_size == 0) {
224*4882a593Smuzhiyun 		/* No extra arguments. Trivial to handle */
225*4882a593Smuzhiyun 		ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u));
226*4882a593Smuzhiyun 	} else {
227*4882a593Smuzhiyun 		struct compat_iw_point *iwp_compat;
228*4882a593Smuzhiyun 		struct iw_point iwp;
229*4882a593Smuzhiyun 
230*4882a593Smuzhiyun 		iwp_compat = (struct compat_iw_point *) &iwr->u.data;
231*4882a593Smuzhiyun 		iwp.pointer = compat_ptr(iwp_compat->pointer);
232*4882a593Smuzhiyun 		iwp.length = iwp_compat->length;
233*4882a593Smuzhiyun 		iwp.flags = iwp_compat->flags;
234*4882a593Smuzhiyun 
235*4882a593Smuzhiyun 		ret = ioctl_private_iw_point(&iwp, cmd, descr,
236*4882a593Smuzhiyun 					     handler, dev, info, extra_size);
237*4882a593Smuzhiyun 
238*4882a593Smuzhiyun 		iwp_compat->pointer = ptr_to_compat(iwp.pointer);
239*4882a593Smuzhiyun 		iwp_compat->length = iwp.length;
240*4882a593Smuzhiyun 		iwp_compat->flags = iwp.flags;
241*4882a593Smuzhiyun 	}
242*4882a593Smuzhiyun 
243*4882a593Smuzhiyun 	/* Call commit handler if needed and defined */
244*4882a593Smuzhiyun 	if (ret == -EIWCOMMIT)
245*4882a593Smuzhiyun 		ret = call_commit_handler(dev);
246*4882a593Smuzhiyun 
247*4882a593Smuzhiyun 	return ret;
248*4882a593Smuzhiyun }
249*4882a593Smuzhiyun #endif
250