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