| /* * ./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, |
| }; |