162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * This file implement the Wireless Extensions priv API. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Authors : Jean Tourrilhes - HPL - <jt@hpl.hp.com> 562306a36Sopenharmony_ci * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved. 662306a36Sopenharmony_ci * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * (As all part of the Linux kernel, this file is GPL) 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/wireless.h> 1262306a36Sopenharmony_ci#include <linux/netdevice.h> 1362306a36Sopenharmony_ci#include <net/iw_handler.h> 1462306a36Sopenharmony_ci#include <net/wext.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ciint iw_handler_get_private(struct net_device * dev, 1762306a36Sopenharmony_ci struct iw_request_info * info, 1862306a36Sopenharmony_ci union iwreq_data * wrqu, 1962306a36Sopenharmony_ci char * extra) 2062306a36Sopenharmony_ci{ 2162306a36Sopenharmony_ci /* Check if the driver has something to export */ 2262306a36Sopenharmony_ci if ((dev->wireless_handlers->num_private_args == 0) || 2362306a36Sopenharmony_ci (dev->wireless_handlers->private_args == NULL)) 2462306a36Sopenharmony_ci return -EOPNOTSUPP; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci /* Check if there is enough buffer up there */ 2762306a36Sopenharmony_ci if (wrqu->data.length < dev->wireless_handlers->num_private_args) { 2862306a36Sopenharmony_ci /* User space can't know in advance how large the buffer 2962306a36Sopenharmony_ci * needs to be. Give it a hint, so that we can support 3062306a36Sopenharmony_ci * any size buffer we want somewhat efficiently... */ 3162306a36Sopenharmony_ci wrqu->data.length = dev->wireless_handlers->num_private_args; 3262306a36Sopenharmony_ci return -E2BIG; 3362306a36Sopenharmony_ci } 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci /* Set the number of available ioctls. */ 3662306a36Sopenharmony_ci wrqu->data.length = dev->wireless_handlers->num_private_args; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci /* Copy structure to the user buffer. */ 3962306a36Sopenharmony_ci memcpy(extra, dev->wireless_handlers->private_args, 4062306a36Sopenharmony_ci sizeof(struct iw_priv_args) * wrqu->data.length); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci return 0; 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/* Size (in bytes) of the various private data types */ 4662306a36Sopenharmony_cistatic const char iw_priv_type_size[] = { 4762306a36Sopenharmony_ci 0, /* IW_PRIV_TYPE_NONE */ 4862306a36Sopenharmony_ci 1, /* IW_PRIV_TYPE_BYTE */ 4962306a36Sopenharmony_ci 1, /* IW_PRIV_TYPE_CHAR */ 5062306a36Sopenharmony_ci 0, /* Not defined */ 5162306a36Sopenharmony_ci sizeof(__u32), /* IW_PRIV_TYPE_INT */ 5262306a36Sopenharmony_ci sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */ 5362306a36Sopenharmony_ci sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */ 5462306a36Sopenharmony_ci 0, /* Not defined */ 5562306a36Sopenharmony_ci}; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic int get_priv_size(__u16 args) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci int num = args & IW_PRIV_SIZE_MASK; 6062306a36Sopenharmony_ci int type = (args & IW_PRIV_TYPE_MASK) >> 12; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci return num * iw_priv_type_size[type]; 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int adjust_priv_size(__u16 args, struct iw_point *iwp) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci int num = iwp->length; 6862306a36Sopenharmony_ci int max = args & IW_PRIV_SIZE_MASK; 6962306a36Sopenharmony_ci int type = (args & IW_PRIV_TYPE_MASK) >> 12; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci /* Make sure the driver doesn't goof up */ 7262306a36Sopenharmony_ci if (max < num) 7362306a36Sopenharmony_ci num = max; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci return num * iw_priv_type_size[type]; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/* 7962306a36Sopenharmony_ci * Wrapper to call a private Wireless Extension handler. 8062306a36Sopenharmony_ci * We do various checks and also take care of moving data between 8162306a36Sopenharmony_ci * user space and kernel space. 8262306a36Sopenharmony_ci * It's not as nice and slimline as the standard wrapper. The cause 8362306a36Sopenharmony_ci * is struct iw_priv_args, which was not really designed for the 8462306a36Sopenharmony_ci * job we are going here. 8562306a36Sopenharmony_ci * 8662306a36Sopenharmony_ci * IMPORTANT : This function prevent to set and get data on the same 8762306a36Sopenharmony_ci * IOCTL and enforce the SET/GET convention. Not doing it would be 8862306a36Sopenharmony_ci * far too hairy... 8962306a36Sopenharmony_ci * If you need to set and get data at the same time, please don't use 9062306a36Sopenharmony_ci * a iw_handler but process it in your ioctl handler (i.e. use the 9162306a36Sopenharmony_ci * old driver API). 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_cistatic int get_priv_descr_and_size(struct net_device *dev, unsigned int cmd, 9462306a36Sopenharmony_ci const struct iw_priv_args **descrp) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci const struct iw_priv_args *descr; 9762306a36Sopenharmony_ci int i, extra_size; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci descr = NULL; 10062306a36Sopenharmony_ci for (i = 0; i < dev->wireless_handlers->num_private_args; i++) { 10162306a36Sopenharmony_ci if (cmd == dev->wireless_handlers->private_args[i].cmd) { 10262306a36Sopenharmony_ci descr = &dev->wireless_handlers->private_args[i]; 10362306a36Sopenharmony_ci break; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci extra_size = 0; 10862306a36Sopenharmony_ci if (descr) { 10962306a36Sopenharmony_ci if (IW_IS_SET(cmd)) { 11062306a36Sopenharmony_ci int offset = 0; /* For sub-ioctls */ 11162306a36Sopenharmony_ci /* Check for sub-ioctl handler */ 11262306a36Sopenharmony_ci if (descr->name[0] == '\0') 11362306a36Sopenharmony_ci /* Reserve one int for sub-ioctl index */ 11462306a36Sopenharmony_ci offset = sizeof(__u32); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* Size of set arguments */ 11762306a36Sopenharmony_ci extra_size = get_priv_size(descr->set_args); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci /* Does it fits in iwr ? */ 12062306a36Sopenharmony_ci if ((descr->set_args & IW_PRIV_SIZE_FIXED) && 12162306a36Sopenharmony_ci ((extra_size + offset) <= IFNAMSIZ)) 12262306a36Sopenharmony_ci extra_size = 0; 12362306a36Sopenharmony_ci } else { 12462306a36Sopenharmony_ci /* Size of get arguments */ 12562306a36Sopenharmony_ci extra_size = get_priv_size(descr->get_args); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci /* Does it fits in iwr ? */ 12862306a36Sopenharmony_ci if ((descr->get_args & IW_PRIV_SIZE_FIXED) && 12962306a36Sopenharmony_ci (extra_size <= IFNAMSIZ)) 13062306a36Sopenharmony_ci extra_size = 0; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci *descrp = descr; 13462306a36Sopenharmony_ci return extra_size; 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic int ioctl_private_iw_point(struct iw_point *iwp, unsigned int cmd, 13862306a36Sopenharmony_ci const struct iw_priv_args *descr, 13962306a36Sopenharmony_ci iw_handler handler, struct net_device *dev, 14062306a36Sopenharmony_ci struct iw_request_info *info, int extra_size) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci char *extra; 14362306a36Sopenharmony_ci int err; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci /* Check what user space is giving us */ 14662306a36Sopenharmony_ci if (IW_IS_SET(cmd)) { 14762306a36Sopenharmony_ci if (!iwp->pointer && iwp->length != 0) 14862306a36Sopenharmony_ci return -EFAULT; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (iwp->length > (descr->set_args & IW_PRIV_SIZE_MASK)) 15162306a36Sopenharmony_ci return -E2BIG; 15262306a36Sopenharmony_ci } else if (!iwp->pointer) 15362306a36Sopenharmony_ci return -EFAULT; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci extra = kzalloc(extra_size, GFP_KERNEL); 15662306a36Sopenharmony_ci if (!extra) 15762306a36Sopenharmony_ci return -ENOMEM; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* If it is a SET, get all the extra data in here */ 16062306a36Sopenharmony_ci if (IW_IS_SET(cmd) && (iwp->length != 0)) { 16162306a36Sopenharmony_ci if (copy_from_user(extra, iwp->pointer, extra_size)) { 16262306a36Sopenharmony_ci err = -EFAULT; 16362306a36Sopenharmony_ci goto out; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci /* Call the handler */ 16862306a36Sopenharmony_ci err = handler(dev, info, (union iwreq_data *) iwp, extra); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* If we have something to return to the user */ 17162306a36Sopenharmony_ci if (!err && IW_IS_GET(cmd)) { 17262306a36Sopenharmony_ci /* Adjust for the actual length if it's variable, 17362306a36Sopenharmony_ci * avoid leaking kernel bits outside. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_ci if (!(descr->get_args & IW_PRIV_SIZE_FIXED)) 17662306a36Sopenharmony_ci extra_size = adjust_priv_size(descr->get_args, iwp); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (copy_to_user(iwp->pointer, extra, extra_size)) 17962306a36Sopenharmony_ci err = -EFAULT; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ciout: 18362306a36Sopenharmony_ci kfree(extra); 18462306a36Sopenharmony_ci return err; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ciint ioctl_private_call(struct net_device *dev, struct iwreq *iwr, 18862306a36Sopenharmony_ci unsigned int cmd, struct iw_request_info *info, 18962306a36Sopenharmony_ci iw_handler handler) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci int extra_size = 0, ret = -EINVAL; 19262306a36Sopenharmony_ci const struct iw_priv_args *descr; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci extra_size = get_priv_descr_and_size(dev, cmd, &descr); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci /* Check if we have a pointer to user space data or not. */ 19762306a36Sopenharmony_ci if (extra_size == 0) { 19862306a36Sopenharmony_ci /* No extra arguments. Trivial to handle */ 19962306a36Sopenharmony_ci ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); 20062306a36Sopenharmony_ci } else { 20162306a36Sopenharmony_ci ret = ioctl_private_iw_point(&iwr->u.data, cmd, descr, 20262306a36Sopenharmony_ci handler, dev, info, extra_size); 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci /* Call commit handler if needed and defined */ 20662306a36Sopenharmony_ci if (ret == -EIWCOMMIT) 20762306a36Sopenharmony_ci ret = call_commit_handler(dev); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return ret; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci#ifdef CONFIG_COMPAT 21362306a36Sopenharmony_ciint compat_private_call(struct net_device *dev, struct iwreq *iwr, 21462306a36Sopenharmony_ci unsigned int cmd, struct iw_request_info *info, 21562306a36Sopenharmony_ci iw_handler handler) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci const struct iw_priv_args *descr; 21862306a36Sopenharmony_ci int ret, extra_size; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci extra_size = get_priv_descr_and_size(dev, cmd, &descr); 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci /* Check if we have a pointer to user space data or not. */ 22362306a36Sopenharmony_ci if (extra_size == 0) { 22462306a36Sopenharmony_ci /* No extra arguments. Trivial to handle */ 22562306a36Sopenharmony_ci ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); 22662306a36Sopenharmony_ci } else { 22762306a36Sopenharmony_ci struct compat_iw_point *iwp_compat; 22862306a36Sopenharmony_ci struct iw_point iwp; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci iwp_compat = (struct compat_iw_point *) &iwr->u.data; 23162306a36Sopenharmony_ci iwp.pointer = compat_ptr(iwp_compat->pointer); 23262306a36Sopenharmony_ci iwp.length = iwp_compat->length; 23362306a36Sopenharmony_ci iwp.flags = iwp_compat->flags; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci ret = ioctl_private_iw_point(&iwp, cmd, descr, 23662306a36Sopenharmony_ci handler, dev, info, extra_size); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci iwp_compat->pointer = ptr_to_compat(iwp.pointer); 23962306a36Sopenharmony_ci iwp_compat->length = iwp.length; 24062306a36Sopenharmony_ci iwp_compat->flags = iwp.flags; 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci /* Call commit handler if needed and defined */ 24462306a36Sopenharmony_ci if (ret == -EIWCOMMIT) 24562306a36Sopenharmony_ci ret = call_commit_handler(dev); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci return ret; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci#endif 250