blob: 9e5ac803d4b4869c1ac43b6384443cc4bd8d2e2c [file] [log] [blame]
/* * ./kernel_modules/pse/pse-sysfs.c
*
* This file defines the sysfs interface for the PSE class
*
* Author: Cradlepoint Technology, Inc. <source@cradlepoint.com>
* Kyle Swenson <kswenson@cradlepoint.com>
*
* Copyright 2017 Cradlepoint Technology, Inc.
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/idr.h>
#include <linux/spinlock.h>
#include <linux/list_sort.h>
#include <linux/sort.h>
#include "pse-port.h"
#include "pse-class.h"
#include "pse-sysfs.h"
char *pse_detect_strings[] = {
"0:DETECT_NO_DEVICE",
"1:DETECT_NORMAL_DEVICE", /* Short */
"2:DETECT_LEGACY_DEVICE", /* High cap devices */
"3:DETECT_POWERED_DEVICE",/* Signature resistance good */ /* Maybe we want to even break this down into detect Type of device iff that's a detect-time thing */
"4:DETECT_ERROR",
NULL
};
EXPORT_SYMBOL(pse_detect_strings);
char *pse_class_strings[] = {
"0:PD_CLASS_UNKNOWN",
"1:PD_CLASS_1",
"2:PD_CLASS_2",
"3:PD_CLASS_3",
"4:PD_CLASS_4",
"5:PD_CLASS_5",
"6:PD_CLASS_6",
"7:PD_CLASS_7",
"8:PD_CLASS_8",
"1D:PD_CLASS_1D",
"2D:PD_CLASS_2D",
"3D:PD_CLASS_3D",
"4D:PD_CLASS_4D",
"5D:PD_CLASS_5D",
"9:PD_CLASS_INVALID",
NULL
};
EXPORT_SYMBOL(pse_class_strings);
uint16_t pse_port_current_table[] = {
375, /* Class 0 (unknown) */
112, /* Class 1 */
206, /* Class 2 */
375, /* Class 3 */
638, /* Class 4 (Type 2) */
938, /* Class 5 */
1250, /* Class 6 */
1563, /* Class 7 */
1875, /* Class 8 */
2*112, /* Class 1D */
2*206, /* Class 2D */
2*375, /* Class 3D */
2*638, /* Class 4D */
2*938 /* Class 5D */
};
uint32_t pse_port_power_table[] = {
15400, /* Class 0 (unknown) */
4000, /* Class 1 */
7000, /* Class 2 */
15400, /* Class 3 */
30000, /* Class 4 */
45000, /* Class 5 */
60000, /* Class 6 */
75000, /* Class 7 */
90000, /* Class 8 */
2*4000, /* Class 1D */
2*7000, /* Class 2D */
2*15400, /* Class 3D */
2*30000, /* Class 4D */
2*45000, /* Class 5D */
};
EXPORT_SYMBOL(pse_port_power_table);
/* ============ PSE CLASS ATTRIBUTES =================
* PSE Class level attributes; these appear under /sys/class/pse/
*/
static ssize_t
pse_version_show(struct class *class, struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", PSE_VERSION_STR);
}
static struct class_attribute pse_version = __ATTR_RO(pse_version);
/* -1 for false
* 0 for no idea
* +1 for true
*/
static int is_trueish(const char *buf, size_t len)
{
if (len == 0)
return 0;
switch (buf[0]) {
case '1':
/* ASCII char 0x31 => '1' means true*/
case 'y':
case 'Y':
/* [yY].* */
case 't':
case 'T':
/* [tT].* */
case 'e':
case 'E':
/* [eE] for enable */
return 1;
case '0':
/* ASCII char 0x30 => '0' means false */
case 'n':
case 'N':
/* [nN].* for No */
case 'F':
case 'f':
/* [fF].* for false */
case 'd':
case 'D':
/* [dD] for disable*/
return -1;
case 'o':
case 'O':
/* Could be On, or Off need to look more */
break;
default:
/* Bail if we don't recognize the first char */
return 0;
}
/*If we're here we know the first char is [oO] */
/* But if we've only got one char in the buffer, we can't determine so bail*/
if (len == 1)
return 0;
/* Here, we know len > 1, so at least two */
switch (buf[1]) {
case 'n':
case 'N':
/* for [oO][nN].* */
return 1;
case 'f':
case 'F':
/* for [oO][fF].* */
return -1;
default:
return 0;
}
return 0;
}
static ssize_t
port_priority_show(struct class *class, struct class_attribute *attr, char *buf)
{
struct class_dev_iter iter;
struct device *dev;
int bput = 0;
class_dev_iter_init(&iter, class, NULL, NULL);
dev = class_dev_iter_next(&iter);
while (dev) {
bput += snprintf((char *)(buf+bput), 32, "port%d -> priority %d\n", to_pse_port_device(dev)->id, to_pse_port_device(dev)->priority);
dev = class_dev_iter_next(&iter);
}
class_dev_iter_exit(&iter);
return bput;
}
/* We are implying port and specifying priority:
* |Port 0 relative priority 2
* | |Port 1 is priority 3
* | | |Port 2 has priority 1
* | | | |Port 3 is priority 0 (highest)
* 2 3 1 0
*
*/
static ssize_t
port_priority_store(struct class *class, struct class_attribute *attr, const char *buff, size_t count)
{
struct device *dev;
struct class_dev_iter iter;
char *current_port_priority;
char *port_priorities;
char *local_buff;
int vi = 0;
size_t bget = 0;
uint8_t port_priority = 0;
struct pse_data *pse_data = get_pse_data(class);
struct pse_port_device *port;
local_buff = kzalloc(sizeof(char) * (count + 1), GFP_KERNEL);
if (!local_buff)
return -ENOMEM;
memcpy(local_buff, buff, count);
port_priorities=local_buff;
pr_debug("buff=%s, count=%zu\n", port_priorities, count);
mutex_lock(&pse_data->pse_lock);
while (bget <= count && vi < PSE_MAX_PORTS) {
current_port_priority = strsep(&port_priorities, " ");
if (current_port_priority == NULL) {
break;
}
pr_debug("input=%s port_priorities=%s Current_port_priority=%s bget=%zu\n", buff, port_priorities, current_port_priority, bget);
if (kstrtou8(current_port_priority, 0, &port_priority) < 0) {
break;
}
pse_data->port_priorities[vi++] = port_priority;
bget += strlen(current_port_priority) + 1;
}
class_dev_iter_init(&iter, class, NULL, NULL);
dev = class_dev_iter_next(&iter);
while (dev) {
port = to_pse_port_device(dev);
port->priority = pse_data->port_priorities[port->id];
dev_dbg(dev, "Storing port_priority %hhu to port%u", port->priority, port->id);
dev = class_dev_iter_next(&iter);
}
class_dev_iter_exit(&iter);
relink_priority_list(class); /* Rebuild the dubly linked list */
redistribute_power(class); /* Force every port off & on again, to get them to repower in correct, priority order */
mutex_unlock(&pse_data->pse_lock);
kfree(local_buff);
return count;
}
static struct class_attribute port_priority = __ATTR_RW(port_priority);
static ssize_t
power_budget_show(struct class *class, struct class_attribute *attr, char *buf)
{
struct pse_data *pse_data = get_pse_data(class);
return sprintf(buf, "%d", pse_data->power_budget);
}
static ssize_t
power_budget_store(struct class *class, struct class_attribute *attr, const char *buff, size_t count)
{
int new;
int res;
struct pse_data *pse_data = get_pse_data(class);
res = kstrtoint(buff, 0, &new);
if (res != 0)
return res;
mutex_lock(&pse_data->pse_lock);
pse_data->power_budget = new;
redistribute_power(&pse_data->cls);
mutex_unlock(&pse_data->pse_lock);
return count;
}
static struct class_attribute power_budget = __ATTR_RW(power_budget);
static ssize_t
enable_show(struct class *class, struct class_attribute *attr, char *buf)
{
struct pse_data *pse_data = get_pse_data(class);
return sprintf(buf, pse_data->enabled ? "True" : "False");
}
static ssize_t
enable_store(struct class *class, struct class_attribute *attr, const char *buf, size_t count)
{
struct pse_data *pse_data = get_pse_data(class);
int t = is_trueish(buf, count);
if (t == 0)
return -EINVAL;
mutex_lock(&pse_data->pse_lock);
if (t > 0)
pse_data->enabled = 1;
else
pse_data->enabled = 0;
redistribute_power(class);
mutex_unlock(&pse_data->pse_lock);
return count;
}
static struct class_attribute enable = __ATTR_RW(enable);
static ssize_t
power_used_show(struct class *class, struct class_attribute *attr, char *buf)
{
struct pse_data *pse_data = get_pse_data(class);
return sprintf(buf, "%d", pse_data->power_used);
}
static struct class_attribute power_used = __ATTR_RO(power_used);
int pse_class_sysfs_init(struct class *class)
{
int err;
err = class_create_file(class, &pse_version);
if (err) {
pr_err("couldn't register pse class version file\n");
return err;
}
err = class_create_file(class, &port_priority);
if (err) {
pr_err("couldn't register pse class port priority file\n");
return err;
}
err = class_create_file(class, &power_budget);
if (err) {
pr_err("couldn't register pse class port priority file\n");
return err;
}
err = class_create_file(class, &enable);
if (err) {
pr_err("couldn't register pse class enable file\n");
return err;
}
err = class_create_file(class, &power_used);
if (err) {
pr_err("couldn't register pse class enable file\n");
return err;
}
return 0;
}
void pse_class_sysfs_close(struct class *class)
{
class_remove_file(class, &pse_version);
class_remove_file(class, &port_priority);
class_remove_file(class, &power_budget);
class_remove_file(class, &enable);
class_remove_file(class, &power_used);
}
/* ============= PSE PORT DEVICE ATTRIBUTES ==================
* These are attributes that appear at /sys/class/pse/portX/<attribute>
* These Attributes DO NOT EXIST until a device driver has registered a port device with the pse class!
*/
/* current_show : Display the current in mA being sourced by the port
*/
static ssize_t
pd_current_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int pd_current = 0;
struct pse_port_device *port = to_pse_port_device(dev);
dev_dbg(dev, "%s:%d\n", __func__, __LINE__);
if (!port || !port->ops->pd_current || !port->ops->pd_powered)
return -EINVAL;
if (port->enabled && port->ops->pd_powered(dev))
pd_current = to_pse_port_device(dev)->ops->pd_current(dev);
return sprintf(buf, "%d\n", pd_current);
}
static struct device_attribute port_current = __ATTR_RO(pd_current);
/* voltage_show: Show the port's voltage
*/
static ssize_t
port_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,
to_pse_port_device(dev)->enabled ? "True" : "False");
}
static ssize_t
port_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int t = is_trueish(buf, count);
struct pse_port_device *port = to_pse_port_device(dev);
if (t == 0)
return -EINVAL;
mutex_lock(&port->lock);
if (t < 0) {
port->enabled = 0;
queue_work(port->wq, &port->disconnect);
} else {
port->enabled = 1;
}
mutex_unlock(&port->lock);
return count;
}
static struct device_attribute port_enable = __ATTR_RW(port_enable);
/* voltage_show: Show the port's voltage
*/
static ssize_t
pd_voltage_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int pd_voltage = 0;
struct pse_port_device *port = to_pse_port_device(dev);
if (!port || !port->ops->pd_voltage || !port->ops->pd_powered)
return -EINVAL;
if (port->enabled && port->ops->pd_powered(dev)) {
pd_voltage = port->ops->pd_voltage(dev);
}
return sprintf(buf, "%d\n", pd_voltage);
}
static struct device_attribute port_voltage = __ATTR_RO(pd_voltage);
/* class_show : Show the port's last classification result
*/
static ssize_t
class_show(struct device *dev, struct device_attribute *attr, char *buf)
{
uint8_t lc;
struct pse_port_device *p = to_pse_port_device(dev);
if (!p->enabled) {
return sprintf(buf, "%s", pse_class_strings[CLASS_INVALID]);
}
if (p->ops->update_classification) {
p->ops->update_classification(dev);
} else {
mutex_lock(&p->lock);
p->last_class_result = p->ops->last_classification(dev);
mutex_unlock(&p->lock);
}
mutex_lock(&p->lock);
lc = p->last_class_result;
mutex_unlock(&p->lock);
if (lc > CLASS_INVALID) {
dev_warn(dev, "PSE Class saw invalid class code 0x%02X\n", lc);
lc = CLASS_INVALID;
}
return sprintf(buf, "%s", pse_class_strings[lc]);
}
static ssize_t
class_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int t;
struct pse_port_device *port = to_pse_port_device(dev);
if (!port || !port->ops->enable_classify)
return -EINVAL;
t = is_trueish(buf, count);
if (t == 0)
return -EINVAL;
if (t < 0) {
dev_dbg(dev, "disabling classification for port %d\n", to_pse_port_device(dev)->port_id);
port->ops->enable_classify(dev, 0);
} else {
dev_dbg(dev, "enabling classification for port %d\n", to_pse_port_device(dev)->port_id);
port->ops->enable_classify(dev, 1);
}
return count;
}
static struct device_attribute port_class = __ATTR_RW(class);
/* detect_show : show the ports' last detection_result
*/
static ssize_t
detect_show(struct device *dev, struct device_attribute *attr, char *buf)
{
uint8_t detval;
struct pse_port_device *p = to_pse_port_device(dev);
if (!p->enabled) {
return sprintf(buf, "%s", pse_detect_strings[DETECT_NO_DEVICE]);
}
if (p->ops->update_detection) {
p->ops->update_detection(dev);
} else {
mutex_lock(&p->lock);
p->last_detect_result = p->ops->last_detection(dev);
mutex_unlock(&p->lock);
}
mutex_lock(&p->lock);
detval = p->last_detect_result;
mutex_unlock(&p->lock);
if (detval >= PSE_NUM_DETECT_EVENTS) {
dev_warn(dev, "PSE class saw an invalid detection code 0x%02x\n", detval);
detval = DETECT_ERROR;
}
return sprintf(buf, "%s", pse_detect_strings[detval]);
}
static ssize_t
detect_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int enable;
struct pse_port_device *port = to_pse_port_device(dev);
if (!port || !port->ops->enable_detect)
return -EINVAL;
enable = is_trueish(buf, count);
if (enable == 0)
return -EINVAL;
if (enable < 0) {
port->ops->enable_detect(dev, 0);
dev_dbg(dev, "disabling detection for port %d\n", to_pse_port_device(dev)->port_id);
} else {
port->ops->enable_detect(dev, 1);
dev_dbg(dev, "enabling detection for port %d\n", to_pse_port_device(dev)->port_id);
}
return count;
}
static struct device_attribute port_detect = __ATTR_RW(detect);
/*port_power_allocation_show: Show the allocation for this port
*/
static ssize_t
port_power_allocation_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct pse_port_device *port = to_pse_port_device(dev);
if (!port)
return -EINVAL;
return sprintf(buf, "%d", port->power_allocation);
}
/*port_power_allocation_store: Request a power allocation change for this port
*/
static ssize_t
port_power_allocation_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
uint32_t power_request;
struct pse_port_device *port = to_pse_port_device(dev);
if (!port)
return -EINVAL;
if (kstrtou32(buf, 0, &power_request) != 0)
return -EINVAL;
dev_info(dev, "Got %u mW for power request\n", power_request);
mutex_lock(&port->lock);
port->power_request = power_request;
queue_work(port->wq, &port->request_power);
mutex_unlock(&port->lock);
return count;
}
static struct device_attribute port_power_allocation = __ATTR_RW(port_power_allocation);
/* port_power_store : power "on/off" a port */
static ssize_t
port_power_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int t;
dev_dbg(dev, "%s:%d\n", __func__, __LINE__);
if (!to_pse_port_device(dev)->ops->apply_power)
return -EINVAL;
t = is_trueish(buf, count);
if (t == 0)
return -EINVAL;
if (t > 0)
to_pse_port_device(dev)->ops->apply_power(dev, 1);
else
to_pse_port_device(dev)->ops->apply_power(dev, 0);
return count;
}
static ssize_t
port_power_show(struct device *dev, struct device_attribute *attr, char *buf)
{
if (!to_pse_port_device(dev)->ops->pd_powered)
return -EINVAL;
return sprintf(buf, to_pse_port_device(dev)->ops->pd_powered(dev) ? "True" : "False");
}
static struct device_attribute port_power = __ATTR_RW(port_power);
static ssize_t
config_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct pse_port_config config;
if (!to_pse_port_device(dev)->ops->get_config)
return -EINVAL;
to_pse_port_device(dev)->ops->get_config(dev, &config);
return sprintf(buf, "enabled:%s\nmanaged:%s\ntype:%d\n"
"disconnect_detection:%s\npd_detection_enabled:%s\n"
"pd_classification_enabled:%s\n"
"legacy_device_enable:%s\ncurrent_limit:%d\n",
(config.enabled ? "True" : "False"),
(config.managed ? "True" : "False"),
(config.port_type+1),
(config.disconnect_event ? "True" : "False"),
(config.detect_enable ? "True" : "False"),
(config.classify_enable ? "True" : "False"),
(config.legacy_enable ? "True" : "False"),
config.current_limit);
}
static struct device_attribute port_config = __ATTR_RO(config);
static ssize_t
reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
if (!to_pse_port_device(dev)->ops->reset)
return -EINVAL;
to_pse_port_device(dev)->ops->reset(dev);
return count;
}
static struct device_attribute port_reset = __ATTR_WO(reset);
struct attribute *dev_attrs[] = {
&port_current.attr,
&port_voltage.attr,
&port_class.attr,
&port_detect.attr,
&port_power.attr,
&port_power_allocation.attr,
&port_enable.attr,
&port_config.attr,
&port_reset.attr,
NULL,
};
struct attribute_group dev_attr_group = {
.attrs = dev_attrs,
};
const struct attribute_group *pse_port_dev_attr_groups[] = {
&dev_attr_group,
NULL,
};