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