18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * This file implement the Wireless Extensions priv API. 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Authors : Jean Tourrilhes - HPL - <jt@hpl.hp.com> 58c2ecf20Sopenharmony_ci * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved. 68c2ecf20Sopenharmony_ci * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * (As all part of the Linux kernel, this file is GPL) 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci#include <linux/slab.h> 118c2ecf20Sopenharmony_ci#include <linux/wireless.h> 128c2ecf20Sopenharmony_ci#include <linux/netdevice.h> 138c2ecf20Sopenharmony_ci#include <net/iw_handler.h> 148c2ecf20Sopenharmony_ci#include <net/wext.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ciint iw_handler_get_private(struct net_device * dev, 178c2ecf20Sopenharmony_ci struct iw_request_info * info, 188c2ecf20Sopenharmony_ci union iwreq_data * wrqu, 198c2ecf20Sopenharmony_ci char * extra) 208c2ecf20Sopenharmony_ci{ 218c2ecf20Sopenharmony_ci /* Check if the driver has something to export */ 228c2ecf20Sopenharmony_ci if ((dev->wireless_handlers->num_private_args == 0) || 238c2ecf20Sopenharmony_ci (dev->wireless_handlers->private_args == NULL)) 248c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci /* Check if there is enough buffer up there */ 278c2ecf20Sopenharmony_ci if (wrqu->data.length < dev->wireless_handlers->num_private_args) { 288c2ecf20Sopenharmony_ci /* User space can't know in advance how large the buffer 298c2ecf20Sopenharmony_ci * needs to be. Give it a hint, so that we can support 308c2ecf20Sopenharmony_ci * any size buffer we want somewhat efficiently... */ 318c2ecf20Sopenharmony_ci wrqu->data.length = dev->wireless_handlers->num_private_args; 328c2ecf20Sopenharmony_ci return -E2BIG; 338c2ecf20Sopenharmony_ci } 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci /* Set the number of available ioctls. */ 368c2ecf20Sopenharmony_ci wrqu->data.length = dev->wireless_handlers->num_private_args; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci /* Copy structure to the user buffer. */ 398c2ecf20Sopenharmony_ci memcpy(extra, dev->wireless_handlers->private_args, 408c2ecf20Sopenharmony_ci sizeof(struct iw_priv_args) * wrqu->data.length); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci return 0; 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* Size (in bytes) of the various private data types */ 468c2ecf20Sopenharmony_cistatic const char iw_priv_type_size[] = { 478c2ecf20Sopenharmony_ci 0, /* IW_PRIV_TYPE_NONE */ 488c2ecf20Sopenharmony_ci 1, /* IW_PRIV_TYPE_BYTE */ 498c2ecf20Sopenharmony_ci 1, /* IW_PRIV_TYPE_CHAR */ 508c2ecf20Sopenharmony_ci 0, /* Not defined */ 518c2ecf20Sopenharmony_ci sizeof(__u32), /* IW_PRIV_TYPE_INT */ 528c2ecf20Sopenharmony_ci sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */ 538c2ecf20Sopenharmony_ci sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */ 548c2ecf20Sopenharmony_ci 0, /* Not defined */ 558c2ecf20Sopenharmony_ci}; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int get_priv_size(__u16 args) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci int num = args & IW_PRIV_SIZE_MASK; 608c2ecf20Sopenharmony_ci int type = (args & IW_PRIV_TYPE_MASK) >> 12; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci return num * iw_priv_type_size[type]; 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int adjust_priv_size(__u16 args, struct iw_point *iwp) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci int num = iwp->length; 688c2ecf20Sopenharmony_ci int max = args & IW_PRIV_SIZE_MASK; 698c2ecf20Sopenharmony_ci int type = (args & IW_PRIV_TYPE_MASK) >> 12; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci /* Make sure the driver doesn't goof up */ 728c2ecf20Sopenharmony_ci if (max < num) 738c2ecf20Sopenharmony_ci num = max; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci return num * iw_priv_type_size[type]; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci/* 798c2ecf20Sopenharmony_ci * Wrapper to call a private Wireless Extension handler. 808c2ecf20Sopenharmony_ci * We do various checks and also take care of moving data between 818c2ecf20Sopenharmony_ci * user space and kernel space. 828c2ecf20Sopenharmony_ci * It's not as nice and slimline as the standard wrapper. The cause 838c2ecf20Sopenharmony_ci * is struct iw_priv_args, which was not really designed for the 848c2ecf20Sopenharmony_ci * job we are going here. 858c2ecf20Sopenharmony_ci * 868c2ecf20Sopenharmony_ci * IMPORTANT : This function prevent to set and get data on the same 878c2ecf20Sopenharmony_ci * IOCTL and enforce the SET/GET convention. Not doing it would be 888c2ecf20Sopenharmony_ci * far too hairy... 898c2ecf20Sopenharmony_ci * If you need to set and get data at the same time, please don't use 908c2ecf20Sopenharmony_ci * a iw_handler but process it in your ioctl handler (i.e. use the 918c2ecf20Sopenharmony_ci * old driver API). 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_cistatic int get_priv_descr_and_size(struct net_device *dev, unsigned int cmd, 948c2ecf20Sopenharmony_ci const struct iw_priv_args **descrp) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci const struct iw_priv_args *descr; 978c2ecf20Sopenharmony_ci int i, extra_size; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci descr = NULL; 1008c2ecf20Sopenharmony_ci for (i = 0; i < dev->wireless_handlers->num_private_args; i++) { 1018c2ecf20Sopenharmony_ci if (cmd == dev->wireless_handlers->private_args[i].cmd) { 1028c2ecf20Sopenharmony_ci descr = &dev->wireless_handlers->private_args[i]; 1038c2ecf20Sopenharmony_ci break; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci extra_size = 0; 1088c2ecf20Sopenharmony_ci if (descr) { 1098c2ecf20Sopenharmony_ci if (IW_IS_SET(cmd)) { 1108c2ecf20Sopenharmony_ci int offset = 0; /* For sub-ioctls */ 1118c2ecf20Sopenharmony_ci /* Check for sub-ioctl handler */ 1128c2ecf20Sopenharmony_ci if (descr->name[0] == '\0') 1138c2ecf20Sopenharmony_ci /* Reserve one int for sub-ioctl index */ 1148c2ecf20Sopenharmony_ci offset = sizeof(__u32); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci /* Size of set arguments */ 1178c2ecf20Sopenharmony_ci extra_size = get_priv_size(descr->set_args); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci /* Does it fits in iwr ? */ 1208c2ecf20Sopenharmony_ci if ((descr->set_args & IW_PRIV_SIZE_FIXED) && 1218c2ecf20Sopenharmony_ci ((extra_size + offset) <= IFNAMSIZ)) 1228c2ecf20Sopenharmony_ci extra_size = 0; 1238c2ecf20Sopenharmony_ci } else { 1248c2ecf20Sopenharmony_ci /* Size of get arguments */ 1258c2ecf20Sopenharmony_ci extra_size = get_priv_size(descr->get_args); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* Does it fits in iwr ? */ 1288c2ecf20Sopenharmony_ci if ((descr->get_args & IW_PRIV_SIZE_FIXED) && 1298c2ecf20Sopenharmony_ci (extra_size <= IFNAMSIZ)) 1308c2ecf20Sopenharmony_ci extra_size = 0; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci *descrp = descr; 1348c2ecf20Sopenharmony_ci return extra_size; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic int ioctl_private_iw_point(struct iw_point *iwp, unsigned int cmd, 1388c2ecf20Sopenharmony_ci const struct iw_priv_args *descr, 1398c2ecf20Sopenharmony_ci iw_handler handler, struct net_device *dev, 1408c2ecf20Sopenharmony_ci struct iw_request_info *info, int extra_size) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci char *extra; 1438c2ecf20Sopenharmony_ci int err; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci /* Check what user space is giving us */ 1468c2ecf20Sopenharmony_ci if (IW_IS_SET(cmd)) { 1478c2ecf20Sopenharmony_ci if (!iwp->pointer && iwp->length != 0) 1488c2ecf20Sopenharmony_ci return -EFAULT; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci if (iwp->length > (descr->set_args & IW_PRIV_SIZE_MASK)) 1518c2ecf20Sopenharmony_ci return -E2BIG; 1528c2ecf20Sopenharmony_ci } else if (!iwp->pointer) 1538c2ecf20Sopenharmony_ci return -EFAULT; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci extra = kzalloc(extra_size, GFP_KERNEL); 1568c2ecf20Sopenharmony_ci if (!extra) 1578c2ecf20Sopenharmony_ci return -ENOMEM; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* If it is a SET, get all the extra data in here */ 1608c2ecf20Sopenharmony_ci if (IW_IS_SET(cmd) && (iwp->length != 0)) { 1618c2ecf20Sopenharmony_ci if (copy_from_user(extra, iwp->pointer, extra_size)) { 1628c2ecf20Sopenharmony_ci err = -EFAULT; 1638c2ecf20Sopenharmony_ci goto out; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci /* Call the handler */ 1688c2ecf20Sopenharmony_ci err = handler(dev, info, (union iwreq_data *) iwp, extra); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* If we have something to return to the user */ 1718c2ecf20Sopenharmony_ci if (!err && IW_IS_GET(cmd)) { 1728c2ecf20Sopenharmony_ci /* Adjust for the actual length if it's variable, 1738c2ecf20Sopenharmony_ci * avoid leaking kernel bits outside. 1748c2ecf20Sopenharmony_ci */ 1758c2ecf20Sopenharmony_ci if (!(descr->get_args & IW_PRIV_SIZE_FIXED)) 1768c2ecf20Sopenharmony_ci extra_size = adjust_priv_size(descr->get_args, iwp); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (copy_to_user(iwp->pointer, extra, extra_size)) 1798c2ecf20Sopenharmony_ci err = -EFAULT; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ciout: 1838c2ecf20Sopenharmony_ci kfree(extra); 1848c2ecf20Sopenharmony_ci return err; 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ciint ioctl_private_call(struct net_device *dev, struct iwreq *iwr, 1888c2ecf20Sopenharmony_ci unsigned int cmd, struct iw_request_info *info, 1898c2ecf20Sopenharmony_ci iw_handler handler) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci int extra_size = 0, ret = -EINVAL; 1928c2ecf20Sopenharmony_ci const struct iw_priv_args *descr; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci extra_size = get_priv_descr_and_size(dev, cmd, &descr); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* Check if we have a pointer to user space data or not. */ 1978c2ecf20Sopenharmony_ci if (extra_size == 0) { 1988c2ecf20Sopenharmony_ci /* No extra arguments. Trivial to handle */ 1998c2ecf20Sopenharmony_ci ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); 2008c2ecf20Sopenharmony_ci } else { 2018c2ecf20Sopenharmony_ci ret = ioctl_private_iw_point(&iwr->u.data, cmd, descr, 2028c2ecf20Sopenharmony_ci handler, dev, info, extra_size); 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci /* Call commit handler if needed and defined */ 2068c2ecf20Sopenharmony_ci if (ret == -EIWCOMMIT) 2078c2ecf20Sopenharmony_ci ret = call_commit_handler(dev); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci return ret; 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT 2138c2ecf20Sopenharmony_ciint compat_private_call(struct net_device *dev, struct iwreq *iwr, 2148c2ecf20Sopenharmony_ci unsigned int cmd, struct iw_request_info *info, 2158c2ecf20Sopenharmony_ci iw_handler handler) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci const struct iw_priv_args *descr; 2188c2ecf20Sopenharmony_ci int ret, extra_size; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci extra_size = get_priv_descr_and_size(dev, cmd, &descr); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci /* Check if we have a pointer to user space data or not. */ 2238c2ecf20Sopenharmony_ci if (extra_size == 0) { 2248c2ecf20Sopenharmony_ci /* No extra arguments. Trivial to handle */ 2258c2ecf20Sopenharmony_ci ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); 2268c2ecf20Sopenharmony_ci } else { 2278c2ecf20Sopenharmony_ci struct compat_iw_point *iwp_compat; 2288c2ecf20Sopenharmony_ci struct iw_point iwp; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci iwp_compat = (struct compat_iw_point *) &iwr->u.data; 2318c2ecf20Sopenharmony_ci iwp.pointer = compat_ptr(iwp_compat->pointer); 2328c2ecf20Sopenharmony_ci iwp.length = iwp_compat->length; 2338c2ecf20Sopenharmony_ci iwp.flags = iwp_compat->flags; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci ret = ioctl_private_iw_point(&iwp, cmd, descr, 2368c2ecf20Sopenharmony_ci handler, dev, info, extra_size); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci iwp_compat->pointer = ptr_to_compat(iwp.pointer); 2398c2ecf20Sopenharmony_ci iwp_compat->length = iwp.length; 2408c2ecf20Sopenharmony_ci iwp_compat->flags = iwp.flags; 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci /* Call commit handler if needed and defined */ 2448c2ecf20Sopenharmony_ci if (ret == -EIWCOMMIT) 2458c2ecf20Sopenharmony_ci ret = call_commit_handler(dev); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci return ret; 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ci#endif 250