/* * WiMedia Logical Link Control Protocol (WLP) * sysfs functions * * Copyright (C) 2007 Intel Corporation * Reinette Chatre * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * FIXME: Docs * */ #include #include "wlp-internal.h" static size_t wlp_wss_wssid_e_print(char *buf, size_t bufsize, struct wlp_wssid_e *wssid_e) { size_t used = 0; used += scnprintf(buf, bufsize, " WSS: "); used += wlp_wss_uuid_print(buf + used, bufsize - used, &wssid_e->wssid); if (wssid_e->info != NULL) { used += scnprintf(buf + used, bufsize - used, " "); used += uwb_mac_addr_print(buf + used, bufsize - used, &wssid_e->info->bcast); used += scnprintf(buf + used, bufsize - used, " %u %u %s\n", wssid_e->info->accept_enroll, wssid_e->info->sec_status, wssid_e->info->name); } return used; } /** * Print out information learned from neighbor discovery * * Some fields being printed may not be included in the device discovery * information (it is not mandatory). We are thus careful how the * information is printed to ensure it is clear to the user what field is * being referenced. * The information being printed is for one time use - temporary storage is * cleaned after it is printed. * * Ideally sysfs output should be on one line. The information printed here * contain a few strings so it will be hard to parse if they are all * printed on the same line - without agreeing on a standard field * separator. */ static ssize_t wlp_wss_neighborhood_print_remove(struct wlp *wlp, char *buf, size_t bufsize) { size_t used = 0; struct wlp_neighbor_e *neighb; struct wlp_wssid_e *wssid_e; mutex_lock(&wlp->nbmutex); used = scnprintf(buf, bufsize, "#Neighbor information\n" "#uuid dev_addr\n" "# Device Name:\n# Model Name:\n# Manufacturer:\n" "# Model Nr:\n# Serial:\n" "# Pri Dev type: CategoryID OUI OUISubdiv " "SubcategoryID\n" "# WSS: WSSID WSS_name accept_enroll sec_status " "bcast\n" "# WSS: WSSID WSS_name accept_enroll sec_status " "bcast\n\n"); list_for_each_entry(neighb, &wlp->neighbors, node) { if (bufsize - used <= 0) goto out; used += wlp_wss_uuid_print(buf + used, bufsize - used, &neighb->uuid); buf[used++] = ' '; used += uwb_dev_addr_print(buf + used, bufsize - used, &neighb->uwb_dev->dev_addr); if (neighb->info != NULL) used += scnprintf(buf + used, bufsize - used, "\n Device Name: %s\n" " Model Name: %s\n" " Manufacturer:%s \n" " Model Nr: %s\n" " Serial: %s\n" " Pri Dev type: " "%u %02x:%02x:%02x %u %u\n", neighb->info->name, neighb->info->model_name, neighb->info->manufacturer, neighb->info->model_nr, neighb->info->serial, neighb->info->prim_dev_type.category, neighb->info->prim_dev_type.OUI[0], neighb->info->prim_dev_type.OUI[1], neighb->info->prim_dev_type.OUI[2], neighb->info->prim_dev_type.OUIsubdiv, neighb->info->prim_dev_type.subID); list_for_each_entry(wssid_e, &neighb->wssid, node) { used += wlp_wss_wssid_e_print(buf + used, bufsize - used, wssid_e); } buf[used++] = '\n'; wlp_remove_neighbor_tmp_info(neighb); } out: mutex_unlock(&wlp->nbmutex); return used; } /** * Show properties of all WSS in neighborhood. * * Will trigger a complete discovery of WSS activated by this device and * its neighbors. */ ssize_t wlp_neighborhood_show(struct wlp *wlp, char *buf) { wlp_discover(wlp); return wlp_wss_neighborhood_print_remove(wlp, buf, PAGE_SIZE); } EXPORT_SYMBOL_GPL(wlp_neighborhood_show); static ssize_t __wlp_wss_properties_show(struct wlp_wss *wss, char *buf, size_t bufsize) { ssize_t result; result = wlp_wss_uuid_print(buf, bufsize, &wss->wssid); result += scnprintf(buf + result, bufsize - result, " "); result += uwb_mac_addr_print(buf + result, bufsize - result, &wss->bcast); result += scnprintf(buf + result, bufsize - result, " 0x%02x %u ", wss->hash, wss->secure_status); result += wlp_wss_key_print(buf + result, bufsize - result, wss->master_key); result += scnprintf(buf + result, bufsize - result, " 0x%02x ", wss->tag); result += uwb_mac_addr_print(buf + result, bufsize - result, &wss->virtual_addr); result += scnprintf(buf + result, bufsize - result, " %s", wss->name); result += scnprintf(buf + result, bufsize - result, "\n\n#WSSID\n#WSS broadcast address\n" "#WSS hash\n#WSS secure status\n" "#WSS master key\n#WSS local tag\n" "#WSS local virtual EUI-48\n#WSS name\n"); return result; } /** * Show which WSS is activated. */ ssize_t wlp_wss_activate_show(struct wlp_wss *wss, char *buf) { int result = 0; if (mutex_lock_interruptible(&wss->mutex)) goto out; if (wss->state >= WLP_WSS_STATE_ACTIVE) result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE); else result = scnprintf(buf, PAGE_SIZE, "No local WSS active.\n"); result += scnprintf(buf + result, PAGE_SIZE - result, "\n\n" "# echo WSSID SECURE_STATUS ACCEPT_ENROLLMENT " "NAME #create new WSS\n" "# echo WSSID [DEV ADDR] #enroll in and activate " "existing WSS, can request registrar\n" "#\n" "# WSSID is a 16 byte hex array. Eg. 12 A3 3B ... \n" "# SECURE_STATUS 0 - unsecure, 1 - secure (default)\n" "# ACCEPT_ENROLLMENT 0 - no, 1 - yes (default)\n" "# NAME is the text string identifying the WSS\n" "# DEV ADDR is the device address of neighbor " "that should be registrar. Eg. 32:AB\n"); mutex_unlock(&wss->mutex); out: return result; } EXPORT_SYMBOL_GPL(wlp_wss_activate_show); /** * Create/activate a new WSS or enroll/activate in neighboring WSS * * The user can provide the WSSID of a WSS in which it wants to enroll. * Only the WSSID is necessary if the WSS have been discovered before. If * the WSS has not been discovered before, or the user wants to use a * particular neighbor as its registrar, then the user can also provide a * device address or the neighbor that will be used as registrar. * * A new WSS is created when the user provides a WSSID, secure status, and * WSS name. */ ssize_t wlp_wss_activate_store(struct wlp_wss *wss, const char *buf, size_t size) { ssize_t result = -EINVAL; struct wlp_uuid wssid; struct uwb_dev_addr dev; struct uwb_dev_addr bcast = {.data = {0xff, 0xff} }; char name[65]; unsigned sec_status, accept; memset(name, 0, sizeof(name)); result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx:%02hhx", &wssid.data[0] , &wssid.data[1], &wssid.data[2] , &wssid.data[3], &wssid.data[4] , &wssid.data[5], &wssid.data[6] , &wssid.data[7], &wssid.data[8] , &wssid.data[9], &wssid.data[10], &wssid.data[11], &wssid.data[12], &wssid.data[13], &wssid.data[14], &wssid.data[15], &dev.data[1], &dev.data[0]); if (result == 16 || result == 17) { result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%u %u %64c", &wssid.data[0] , &wssid.data[1], &wssid.data[2] , &wssid.data[3], &wssid.data[4] , &wssid.data[5], &wssid.data[6] , &wssid.data[7], &wssid.data[8] , &wssid.data[9], &wssid.data[10], &wssid.data[11], &wssid.data[12], &wssid.data[13], &wssid.data[14], &wssid.data[15], &sec_status, &accept, name); if (result == 16) result = wlp_wss_enroll_activate(wss, &wssid, &bcast); else if (result == 19) { sec_status = sec_status == 0 ? 0 : 1; accept = accept == 0 ? 0 : 1; /* We read name using %c, so the newline needs to be * removed */ if (strlen(name) != sizeof(name) - 1) name[strlen(name) - 1] = '\0'; result = wlp_wss_create_activate(wss, &wssid, name, sec_status, accept); } else result = -EINVAL; } else if (result == 18) result = wlp_wss_enroll_activate(wss, &wssid, &dev); else result = -EINVAL; return result < 0 ? result : size; } EXPORT_SYMBOL_GPL(wlp_wss_activate_store); /** * Show the UUID of this host */ ssize_t wlp_uuid_show(struct wlp *wlp, char *buf) { ssize_t result = 0; mutex_lock(&wlp->mutex); result = wlp_wss_uuid_print(buf, PAGE_SIZE, &wlp->uuid); buf[result++] = '\n'; mutex_unlock(&wlp->mutex); return result; } EXPORT_SYMBOL_GPL(wlp_uuid_show); /** * Store a new UUID for this host * * According to the spec this should be encoded as an octet string in the * order the octets are shown in string representation in RFC 4122 (WLP * 0.99 [Table 6]) * * We do not check value provided by user. */ ssize_t wlp_uuid_store(struct wlp *wlp, const char *buf, size_t size) { ssize_t result; struct wlp_uuid uuid; mutex_lock(&wlp->mutex); result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx ", &uuid.data[0] , &uuid.data[1], &uuid.data[2] , &uuid.data[3], &uuid.data[4] , &uuid.data[5], &uuid.data[6] , &uuid.data[7], &uuid.data[8] , &uuid.data[9], &uuid.data[10], &uuid.data[11], &uuid.data[12], &uuid.data[13], &uuid.data[14], &uuid.data[15]); if (result != 16) { result = -EINVAL; goto error; } wlp->uuid = uuid; error: mutex_unlock(&wlp->mutex); return result < 0 ? result : size; } EXPORT_SYMBOL_GPL(wlp_uuid_store); /** * Show contents of members of device information structure */ #define wlp_dev_info_show(type) \ ssize_t wlp_dev_##type##_show(struct wlp *wlp, char *buf) \ { \ ssize_t result = 0; \ mutex_lock(&wlp->mutex); \ if (wlp->dev_info == NULL) { \ result = __wlp_setup_device_info(wlp); \ if (result < 0) \ goto out; \ } \ result = scnprintf(buf, PAGE_SIZE, "%s\n", wlp->dev_info->type);\ out: \ mutex_unlock(&wlp->mutex); \ return result; \ } \ EXPORT_SYMBOL_GPL(wlp_dev_##type##_show); wlp_dev_info_show(name) wlp_dev_info_show(model_name) wlp_dev_info_show(model_nr) wlp_dev_info_show(manufacturer) wlp_dev_info_show(serial) /** * Store contents of members of device information structure */ #define wlp_dev_info_store(type, len) \ ssize_t wlp_dev_##type##_store(struct wlp *wlp, const char *buf, size_t size)\ { \ ssize_t result; \ char format[10]; \ mutex_lock(&wlp->mutex); \ if (wlp->dev_info == NULL) { \ result = __wlp_alloc_device_info(wlp); \ if (result < 0) \ goto out; \ } \ memset(wlp->dev_info->type, 0, sizeof(wlp->dev_info->type)); \ sprintf(format, "%%%uc", len); \ result = sscanf(buf, format, wlp->dev_info->type); \ out: \ mutex_unlock(&wlp->mutex); \ return result < 0 ? result : size; \ } \ EXPORT_SYMBOL_GPL(wlp_dev_##type##_store); wlp_dev_info_store(name, 32) wlp_dev_info_store(manufacturer, 64) wlp_dev_info_store(model_name, 32) wlp_dev_info_store(model_nr, 32) wlp_dev_info_store(serial, 32) static const char *__wlp_dev_category[] = { [WLP_DEV_CAT_COMPUTER] = "Computer", [WLP_DEV_CAT_INPUT] = "Input device", [WLP_DEV_CAT_PRINT_SCAN_FAX_COPIER] = "Printer, scanner, FAX, or " "Copier", [WLP_DEV_CAT_CAMERA] = "Camera", [WLP_DEV_CAT_STORAGE] = "Storage Network", [WLP_DEV_CAT_INFRASTRUCTURE] = "Infrastructure", [WLP_DEV_CAT_DISPLAY] = "Display", [WLP_DEV_CAT_MULTIM] = "Multimedia device", [WLP_DEV_CAT_GAMING] = "Gaming device", [WLP_DEV_CAT_TELEPHONE] = "Telephone", [WLP_DEV_CAT_OTHER] = "Other", }; static const char *wlp_dev_category_str(unsigned cat) { if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE) || cat == WLP_DEV_CAT_OTHER) return __wlp_dev_category[cat]; return "unknown category"; } ssize_t wlp_dev_prim_category_show(struct wlp *wlp, char *buf) { ssize_t result = 0; mutex_lock(&wlp->mutex); if (wlp->dev_info == NULL) { result = __wlp_setup_device_info(wlp); if (result < 0) goto out; } result = scnprintf(buf, PAGE_SIZE, "%s\n", wlp_dev_category_str(wlp->dev_info->prim_dev_type.category)); out: mutex_unlock(&wlp->mutex); return result; } EXPORT_SYMBOL_GPL(wlp_dev_prim_category_show); ssize_t wlp_dev_prim_category_store(struct wlp *wlp, const char *buf, size_t size) { ssize_t result; u16 cat; mutex_lock(&wlp->mutex); if (wlp->dev_info == NULL) { result = __wlp_alloc_device_info(wlp); if (result < 0) goto out; } result = sscanf(buf, "%hu", &cat); if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE) || cat == WLP_DEV_CAT_OTHER) wlp->dev_info->prim_dev_type.category = cat; else result = -EINVAL; out: mutex_unlock(&wlp->mutex); return result < 0 ? result : size; } EXPORT_SYMBOL_GPL(wlp_dev_prim_category_store); ssize_t wlp_dev_prim_OUI_show(struct wlp *wlp, char *buf) { ssize_t result = 0; mutex_lock(&wlp->mutex); if (wlp->dev_info == NULL) { result = __wlp_setup_device_info(wlp); if (result < 0) goto out; } result = scnprintf(buf, PAGE_SIZE, "%02x:%02x:%02x\n", wlp->dev_info->prim_dev_type.OUI[0], wlp->dev_info->prim_dev_type.OUI[1], wlp->dev_info->prim_dev_type.OUI[2]); out: mutex_unlock(&wlp->mutex); return result; } EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_show); ssize_t wlp_dev_prim_OUI_store(struct wlp *wlp, const char *buf, size_t size) { ssize_t result; u8 OUI[3]; mutex_lock(&wlp->mutex); if (wlp->dev_info == NULL) { result = __wlp_alloc_device_info(wlp); if (result < 0) goto out; } result = sscanf(buf, "%hhx:%hhx:%hhx", &OUI[0], &OUI[1], &OUI[2]); if (result != 3) { result = -EINVAL; goto out; } else memcpy(wlp->dev_info->prim_dev_type.OUI, OUI, sizeof(OUI)); out: mutex_unlock(&wlp->mutex); return result < 0 ? result : size; } EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_store); ssize_t wlp_dev_prim_OUI_sub_show(struct wlp *wlp, char *buf) { ssize_t result = 0; mutex_lock(&wlp->mutex); if (wlp->dev_info == NULL) { result = __wlp_setup_device_info(wlp); if (result < 0) goto out; } result = scnprintf(buf, PAGE_SIZE, "%u\n", wlp->dev_info->prim_dev_type.OUIsubdiv); out: mutex_unlock(&wlp->mutex); return result; } EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_show); ssize_t wlp_dev_prim_OUI_sub_store(struct wlp *wlp, const char *buf, size_t size) { ssize_t result; unsigned sub; u8 max_sub = ~0; mutex_lock(&wlp->mutex); if (wlp->dev_info == NULL) { result = __wlp_alloc_device_info(wlp); if (result < 0) goto out; } result = sscanf(buf, "%u", &sub); if (sub <= max_sub) wlp->dev_info->prim_dev_type.OUIsubdiv = sub; else result = -EINVAL; out: mutex_unlock(&wlp->mutex); return result < 0 ? result : size; } EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_store); ssize_t wlp_dev_prim_subcat_show(struct wlp *wlp, char *buf) { ssize_t result = 0; mutex_lock(&wlp->mutex); if (wlp->dev_info == NULL) { result = __wlp_setup_device_info(wlp); if (result < 0) goto out; } result = scnprintf(buf, PAGE_SIZE, "%u\n", wlp->dev_info->prim_dev_type.subID); out: mutex_unlock(&wlp->mutex); return result; } EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_show); ssize_t wlp_dev_prim_subcat_store(struct wlp *wlp, const char *buf, size_t size) { ssize_t result; unsigned sub; __le16 max_sub = ~0; mutex_lock(&wlp->mutex); if (wlp->dev_info == NULL) { result = __wlp_alloc_device_info(wlp); if (result < 0) goto out; } result = sscanf(buf, "%u", &sub); if (sub <= max_sub) wlp->dev_info->prim_dev_type.subID = sub; else result = -EINVAL; out: mutex_unlock(&wlp->mutex); return result < 0 ? result : size; } EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_store); /** * Subsystem implementation for interaction with individual WSS via sysfs * * Followed instructions for subsystem in Documentation/filesystems/sysfs.txt */ #define kobj_to_wlp_wss(obj) container_of(obj, struct wlp_wss, kobj) #define attr_to_wlp_wss_attr(_attr) \ container_of(_attr, struct wlp_wss_attribute, attr) /** * Sysfs subsystem: forward read calls * * Sysfs operation for forwarding read call to the show method of the * attribute owner */ static ssize_t wlp_wss_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct wlp_wss_attribute *wss_attr = attr_to_wlp_wss_attr(attr); struct wlp_wss *wss = kobj_to_wlp_wss(kobj); ssize_t ret = -EIO; if (wss_attr->show) ret = wss_attr->show(wss, buf); return ret; } /** * Sysfs subsystem: forward write calls * * Sysfs operation for forwarding write call to the store method of the * attribute owner */ static ssize_t wlp_wss_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct wlp_wss_attribute *wss_attr = attr_to_wlp_wss_attr(attr); struct wlp_wss *wss = kobj_to_wlp_wss(kobj); ssize_t ret = -EIO; if (wss_attr->store) ret = wss_attr->store(wss, buf, count); return ret; } static struct sysfs_ops wss_sysfs_ops = { .show = wlp_wss_attr_show, .store = wlp_wss_attr_store, }; struct kobj_type wss_ktype = { .release = wlp_wss_release, .sysfs_ops = &wss_sysfs_ops, }; /** * Sysfs files for individual WSS */ /** * Print static properties of this WSS * * The name of a WSS may not be null teminated. It's max size is 64 bytes * so we copy it to a larger array just to make sure we print sane data. */ static ssize_t wlp_wss_properties_show(struct wlp_wss *wss, char *buf) { int result = 0; if (mutex_lock_interruptible(&wss->mutex)) goto out; result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE); mutex_unlock(&wss->mutex); out: return result; } WSS_ATTR(properties, S_IRUGO, wlp_wss_properties_show, NULL); /** * Print all connected members of this WSS * The EDA cache contains all members of WSS neighborhood. */ static ssize_t wlp_wss_members_show(struct wlp_wss *wss, char *buf) { struct wlp *wlp = container_of(wss, struct wlp, wss); return wlp_eda_show(wlp, buf); } WSS_ATTR(members, S_IRUGO, wlp_wss_members_show, NULL); static const char *__wlp_strstate[] = { "none", "partially enrolled", "enrolled", "active", "connected", }; static const char *wlp_wss_strstate(unsigned state) { if (state >= ARRAY_SIZE(__wlp_strstate)) return "unknown state"; return __wlp_strstate[state]; } /* * Print current state of this WSS */ static ssize_t wlp_wss_state_show(struct wlp_wss *wss, char *buf) { int result = 0; if (mutex_lock_interruptible(&wss->mutex)) goto out; result = scnprintf(buf, PAGE_SIZE, "%s\n", wlp_wss_strstate(wss->state)); mutex_unlock(&wss->mutex); out: return result; } WSS_ATTR(state, S_IRUGO, wlp_wss_state_show, NULL); static struct attribute *wss_attrs[] = { &wss_attr_properties.attr, &wss_attr_members.attr, &wss_attr_state.attr, NULL, }; struct attribute_group wss_attr_group = { .name = NULL, /* we want them in the same directory */ .attrs = wss_attrs, };