Expose knobs to control the device (induce reset, power saving, querying tx or rx stats, internal debug information and debug level manipulation). Signed-off-by: Inaky Perez-Gonzalez <inaky@xxxxxxxxxxxxxxx> --- drivers/net/wimax/i2400m/sysfs.c | 458 ++++++++++++++++++++++++++++++++++++++ 1 files changed, 458 insertions(+), 0 deletions(-) create mode 100644 drivers/net/wimax/i2400m/sysfs.c diff --git a/drivers/net/wimax/i2400m/sysfs.c b/drivers/net/wimax/i2400m/sysfs.c new file mode 100644 index 0000000..3640719 --- /dev/null +++ b/drivers/net/wimax/i2400m/sysfs.c @@ -0,0 +1,458 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Sysfs interfaces to show driver and device information + * + * + * Copyright (C) 2007 Intel Corporation <linux-wimax@xxxxxxxxx> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@xxxxxxxxx> + * + * 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. + */ + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include "i2400m.h" + + +#define D_SUBMODULE sysfs +#include "debug-levels.h" + +/* + * Cold reset the device (deferred work routine) + * + * Need to use a workstruct because when done from sysfs, the device + * lock is taken, so after a reset, the new device "instance" is + * connected before we have a chance to disconnect the current + * instance. This creates problems for upper layers, as for example + * the management daemon for a while could think we have two wimax + * connections in the system. + * + * Note calling _put before _reset_cold is ok because _put uses + * netdev's dev_put(), which won't free anything. + * + * In any case, it has to be before, as if not we enter a race + * coindition calling reset_cold(); it would try to unregister the + * device, but it will keep the reference count and because reset had + * a device lock...well, big mess. + */ +static +void __i2400m_reset_cold_work(struct work_struct *ws) +{ + struct i2400m_work *iw = + container_of(ws, struct i2400m_work, ws); + i2400m_put(iw->i2400m); + iw->i2400m->bus_reset(iw->i2400m, I2400M_RT_COLD); + kfree(iw); +} + +static +ssize_t i2400m_reset_cold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned val; + + result = -EINVAL; + if (sscanf(buf, "%u\n", &val) != 1) + goto error_no_unsigned; + if (val != 1) + goto error_bad_value; + i2400m_schedule_work(i2400m, __i2400m_reset_cold_work, GFP_KERNEL); + if (result >= 1) + result = size; +error_no_unsigned: +error_bad_value: + return result; +} + +static +DEVICE_ATTR(i2400m_reset_cold, S_IRUGO | S_IWUSR, + NULL, i2400m_reset_cold_store); + + +/* + * Warm reset the device + * + * We just warm reset the device; no need to defer, as the device will + * not disconnect. + */ +static +ssize_t i2400m_reset_warm_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned val; + + result = -EINVAL; + if (sscanf(buf, "%u\n", &val) != 1) + goto error_no_unsigned; + if (val != 1) + goto error_bad_value; + result = i2400m->bus_reset(i2400m, I2400M_RT_WARM); + if (result >= 0) + result = size; +error_no_unsigned: +error_bad_value: + return result; +} + +static +DEVICE_ATTR(i2400m_reset_warm, S_IRUGO | S_IWUSR, + NULL, i2400m_reset_warm_store); + + +/* + * Show RX statistics + * + * Total #payloads | min #payloads in a RX | max #payloads in a RX + * Total #RXs | Total bytes | min #bytes in a RX | max #bytes in a RX + * + * Write 1 to clear. + */ +static +ssize_t i2400m_rx_stats_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned long flags; + + spin_lock_irqsave(&i2400m->rx_lock, flags); + result = snprintf(buf, PAGE_SIZE, "%u %u %u %u %u %u %u\n", + i2400m->rx_pl_num, i2400m->rx_pl_min, + i2400m->rx_pl_max, i2400m->rx_num, + i2400m->rx_size_acc, + i2400m->rx_size_min, i2400m->rx_size_max); + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + return result; +} + +static +ssize_t i2400m_rx_stats_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned val; + unsigned long flags; + + result = -EINVAL; + if (sscanf(buf, "%u\n", &val) != 1) + goto error_no_unsigned; + if (val != 1) + goto error_bad_value; + spin_lock_irqsave(&i2400m->rx_lock, flags); + i2400m->rx_pl_num = 0; + i2400m->rx_pl_max = 0; + i2400m->rx_pl_min = UINT_MAX; + i2400m->rx_num = 0; + i2400m->rx_size_acc = 0; + i2400m->rx_size_min = UINT_MAX; + i2400m->rx_size_max = 0; + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + result = size; +error_no_unsigned: +error_bad_value: + return result; +} + +static +DEVICE_ATTR(i2400m_rx_stats, S_IRUGO | S_IWUSR, + i2400m_rx_stats_show, i2400m_rx_stats_store); + + +/* + * Show TX statistics + * + * Total #payloads | min #payloads in a TX | max #payloads in a TX + * Total #TXs | Total bytes | min #bytes in a TX | max #bytes in a TX + * + * Write 1 to clear. + */ +static +ssize_t i2400m_tx_stats_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned long flags; + + spin_lock_irqsave(&i2400m->tx_lock, flags); + result = snprintf(buf, PAGE_SIZE, "%u %u %u %u %u %u %u\n", + i2400m->tx_pl_num, i2400m->tx_pl_min, + i2400m->tx_pl_max, i2400m->tx_num, + i2400m->tx_size_acc, + i2400m->tx_size_min, i2400m->tx_size_max); + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + return result; +} + +static +ssize_t i2400m_tx_stats_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned val; + unsigned long flags; + + result = -EINVAL; + if (sscanf(buf, "%u\n", &val) != 1) + goto error_no_unsigned; + if (val != 1) + goto error_bad_value; + spin_lock_irqsave(&i2400m->tx_lock, flags); + i2400m->tx_pl_num = 0; + i2400m->tx_pl_max = 0; + i2400m->tx_pl_min = UINT_MAX; + i2400m->tx_num = 0; + i2400m->tx_size_acc = 0; + i2400m->tx_size_min = UINT_MAX; + i2400m->tx_size_max = 0; + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + result = size; +error_no_unsigned: +error_bad_value: + return result; +} + +static +DEVICE_ATTR(i2400m_tx_stats, S_IRUGO | S_IWUSR, + i2400m_tx_stats_show, i2400m_tx_stats_store); + +/* + * Show debug stuff + * + * Don't poke with this unless you know what you are doing. + */ +static +ssize_t i2400m_debug_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result = 0; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + char var[256], val[256]; + + result = -EINVAL; + if (sscanf(buf, "%254s %254s\n", var, val) != 2) { + dev_err(dev, "debug: bad format, expected VARIABLE VALUE\n"); + goto error; + } + + if (!strcmp(var, "state")) { + enum i2400m_system_state state; + if (sscanf(val, "%u", &state) != 1) { + dev_err(dev, "debug/state: can't parse unsigned %s\n", + val); + goto error; + } + if (state < I2400M_SS_UNINITIALIZED || state >= I2400M_SS_MAX) { + dev_err(dev, "debug/state: %u is out of range\n", + state); + goto error; + } + i2400m->state = state; + result = size; + } else + dev_err(dev, "debug: unknown variable %s\n", var); +error: + return result; +} + +/* + * Show debug stuff + */ +static +ssize_t i2400m_debug_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t result = 0; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned long flags; + + result += scnprintf( + buf, PAGE_SIZE, + "Don't poke with this unless you know what you are doing. \n" + "It provides means to modify internal settings in the \n" + "driver that can be used to exercise error paths.\n" + "\n" + "Format for setting them is 'echo FIELD VALUE' (fields \n" + "marked ! can't be set)\n" + "\n"); + + result += scnprintf(buf + result, PAGE_SIZE - result, + "!queue: %s\n", + netif_queue_stopped(to_net_dev(dev)) ? + "stopped" : "running"); + + spin_lock_irqsave(&i2400m->tx_lock, flags); + result += scnprintf( + buf + result, PAGE_SIZE - result, + "!TX FIFO in: %zu\n" + "!TX FIFO out: %zu (%zu used)\n" + "!TX FIFO msg: @%zd\n", + i2400m->tx_in, i2400m->tx_out, + i2400m->tx_out - i2400m->tx_in, + (size_t) (i2400m->tx_msg ? + (void *) i2400m->tx_msg - i2400m->tx_buf : -1)); + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + + result += scnprintf( + buf + result, PAGE_SIZE - result, + "state: %u\n", i2400m->state); + return result; +} +static +DEVICE_ATTR(i2400m_debug, S_IRUGO | S_IWUSR, + i2400m_debug_show, i2400m_debug_store); + + +/* + * Trace received messages from user space + * + * In order to tap the bidirectional message stream in the 'msg' pipe, + * user space can read from the 'msg' pipe; however, due to + * limitations in libnl, we can't know what the different applications + * are sending down to the kernel. + * + * So we have this hack where the driver will echo any message + * received on the msg pipe from user space [through a call to + * wimax_dev->op_msg_from_user() into i2400m_op_msg_from_user()] into + * the 'trace' pipe that this driver creates. + * + * So then, reading from both the 'trace' and 'msg' pipes in user spce + * will provide a full dump of the traffic. + * + * Write 1 to activate, 0 to clear. + * + * It is not really very atomic, but it is also not too critical. + */ +static +ssize_t i2400m_trace_msg_from_user_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + + result = snprintf(buf, PAGE_SIZE, "%u\n", + i2400m->trace_msg_from_user); + return result; +} + +static +ssize_t i2400m_trace_msg_from_user_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned val; + + result = -EINVAL; + if (sscanf(buf, "%u\n", &val) == 1) { + i2400m->trace_msg_from_user = val ? 1 : 0; + result = size; + } + return result; +} + +static +DEVICE_ATTR(i2400m_trace_msg_from_user, S_IRUGO | S_IWUSR, + i2400m_trace_msg_from_user_show, i2400m_trace_msg_from_user_store); + + +/* + * Ask the device to enter power saving mode. + * + * This is not really selective suspend mode, but asking the device to + * enter selective suspend on its own. + */ +static +ssize_t i2400m_suspend_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct i2400m *i2400m = net_dev_to_i2400m(to_net_dev(dev)); + unsigned val; + + result = -EINVAL; + if (sscanf(buf, "%u\n", &val) != 1) + goto error_no_unsigned; + if (val != 1) + goto error_bad_value; + result = i2400m_cmd_enter_powersave(i2400m); + if (result >= 0) + result = size; +error_no_unsigned: +error_bad_value: + return result; +} + +static +DEVICE_ATTR(i2400m_suspend, S_IRUGO | S_IWUSR, + NULL, i2400m_suspend_store); + + +/* + * Debug levels control; see debug.h + */ +struct d_level D_LEVEL[] = { + D_SUBMODULE_DEFINE(control), + D_SUBMODULE_DEFINE(driver), + D_SUBMODULE_DEFINE(fw), + D_SUBMODULE_DEFINE(netdev), + D_SUBMODULE_DEFINE(rfkill), + D_SUBMODULE_DEFINE(rx), + D_SUBMODULE_DEFINE(sysfs), + D_SUBMODULE_DEFINE(tx), +}; +size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL); + +static +DEVICE_ATTR(i2400m_debug_levels, S_IRUGO | S_IWUSR, + d_level_show, d_level_store); + + +static +struct attribute *i2400m_dev_attrs[] = { + &dev_attr_i2400m_reset_cold.attr, + &dev_attr_i2400m_reset_warm.attr, + &dev_attr_i2400m_suspend.attr, + &dev_attr_i2400m_rx_stats.attr, + &dev_attr_i2400m_tx_stats.attr, + &dev_attr_i2400m_debug.attr, + &dev_attr_i2400m_debug_levels.attr, + &dev_attr_i2400m_trace_msg_from_user.attr, + NULL, +}; + +struct attribute_group i2400m_dev_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = i2400m_dev_attrs, +}; -- 1.5.6.5