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