1 /* 2 * This file implement the Wireless Extensions priv API. 3 * 4 * Authors : Jean Tourrilhes - HPL - <jt@hpl.hp.com> 5 * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved. 6 * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> 7 * 8 * (As all part of the Linux kernel, this file is GPL) 9 */ 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 /* Check if the driver has something to export */ 21 if ((dev->wireless_handlers->num_private_args == 0) || 22 (dev->wireless_handlers->private_args == NULL)) 23 return -EOPNOTSUPP; 24 25 /* Check if there is enough buffer up there */ 26 if (wrqu->data.length < dev->wireless_handlers->num_private_args) { 27 /* User space can't know in advance how large the buffer 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 /* Set the number of available ioctls. */ 35 wrqu->data.length = dev->wireless_handlers->num_private_args; 36 37 /* Copy structure to the user buffer. */ 38 memcpy(extra, dev->wireless_handlers->private_args, 39 sizeof(struct iw_priv_args) * wrqu->data.length); 40 41 return 0; 42 } 43 44 /* Size (in bytes) of the various private data types */ 45 static const char iw_priv_type_size[] = { 46 0, /* IW_PRIV_TYPE_NONE */ 47 1, /* IW_PRIV_TYPE_BYTE */ 48 1, /* IW_PRIV_TYPE_CHAR */ 49 0, /* Not defined */ 50 sizeof(__u32), /* IW_PRIV_TYPE_INT */ 51 sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */ 52 sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */ 53 0, /* Not defined */ 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 /* Make sure the driver doesn't goof up */ 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; /* For sub-ioctls */ 110 /* Check for sub-ioctl handler */ 111 if (descr->name[0] == '\0') 112 /* Reserve one int for sub-ioctl index */ 113 offset = sizeof(__u32); 114 115 /* Size of set arguments */ 116 extra_size = get_priv_size(descr->set_args); 117 118 /* Does it fits in iwr ? */ 119 if ((descr->set_args & IW_PRIV_SIZE_FIXED) && 120 ((extra_size + offset) <= IFNAMSIZ)) 121 extra_size = 0; 122 } else { 123 /* Size of get arguments */ 124 extra_size = get_priv_size(descr->get_args); 125 126 /* Does it fits in iwr ? */ 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 /* Check what user space is giving us */ 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 = kmalloc(extra_size, GFP_KERNEL); 155 if (!extra) 156 return -ENOMEM; 157 158 /* If it is a SET, get all the extra data in here */ 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 /* Call the handler */ 167 err = handler(dev, info, (union iwreq_data *) iwp, extra); 168 169 /* If we have something to return to the user */ 170 if (!err && IW_IS_GET(cmd)) { 171 /* Adjust for the actual length if it's variable, 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 /* Check if we have a pointer to user space data or not. */ 196 if (extra_size == 0) { 197 /* No extra arguments. Trivial to handle */ 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 /* Call commit handler if needed and defined */ 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 /* Check if we have a pointer to user space data or not. */ 222 if (extra_size == 0) { 223 /* No extra arguments. Trivial to handle */ 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 iwp.pointer = compat_ptr(iwp_compat->pointer); 231 iwp.length = iwp_compat->length; 232 iwp.flags = iwp_compat->flags; 233 234 ret = ioctl_private_iw_point(&iwp, cmd, descr, 235 handler, dev, info, extra_size); 236 237 iwp_compat->pointer = ptr_to_compat(iwp.pointer); 238 iwp_compat->length = iwp.length; 239 iwp_compat->flags = iwp.flags; 240 } 241 242 /* Call commit handler if needed and defined */ 243 if (ret == -EIWCOMMIT) 244 ret = call_commit_handler(dev); 245 246 return ret; 247 } 248 #endif 249