blob: 0370399ff4bb0b1a134feb89d06fb524725e13f7 [file] [log] [blame]
/*
* WiMedia Logical Link Control Protocol (WLP)
* sysfs functions
*
* Copyright (C) 2007 Intel Corporation
* Reinette Chatre <reinette.chatre@intel.com>
*
* 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 <linux/wlp.h>
#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,
};