blob: 3b27cc5485378cd379ee0e0dee47d7f61e0f08ad [file] [log] [blame]
/*
* ./kernel_modules/pse/pse-class.c
*
* This file defines a device class for 802.3af/at/at+ PSE devices.
*
* 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/workqueue.h>
#include <linux/list_sort.h>
#include <linux/sort.h>
#include "pse-port.h"
#include "pse-sysfs.h"
#include "pse-class.h"
#define DEFSTRLEN 32
static int determine_power_used(struct class *class, struct device *pdev);
static int power_port(struct device *dev);
static int request_power(struct device *dev);
static void pse_port_power_request_handler(struct work_struct *work);
static void pse_port_power_disconnect_handler(struct work_struct *work);
static struct class *g_pse_class;
static DEFINE_IDA(pse_port_ida);
/* match_port_priority: Return the first pse_port_device that has a priority
* equal the priority passed in via data
*/
int match_port_priority(struct device *dev, const void *data)
{
uint8_t priority;
if (data) {
priority = *((uint8_t *)data);
if (to_pse_port_device(dev)->priority == priority)
return 1;/*Match found */
}
return 0; /* no match */
}
int match_port_id(struct device *dev, const void *data)
{
uint8_t port_id;
if (data) {
port_id = *((uint8_t *)data);
if (to_ppd(dev)->id == port_id)
return 1;/*Match found */
}
return 0; /* no match */
}
/* compare_port_priority: if p1 is a lower priority (which means a higher value
* in port->priority) port than p2, return -1 If the ports have the same
* priority, then look at port->id, lower id, higher priority
*
* P1 : priority 1
* P2 : priority 3
* P1 has a higher priority than P2
* compare_port_priority(P1, P2) -> -1
* compare_port_priority(P1, P1) -> 0
* compare_port_priority(P2, P1) -> 1
*
*
*/
int compare_port_priority(struct pse_port_device *p1, struct pse_port_device *p2)
{
if (p1->priority > p2->priority)
return -1;
if (p1->priority < p2->priority)
return 1;
/* (p1->priority == p2->priority) */
if (p1->id > p2->id)
return -1;
if (p1->id < p2->id)
return 1;
/* (p1->priority == p2->priority and p1->id == p2->id) */
return 0;
}
#define LT(p1, p2) (compare_port_priority((p1), (p2)) < 0)
#define GT(p1, p2) (compare_port_priority((p1), (p2)) > 0)
#define EQ(p1, p2) (compare_port_priority((p1), (p2)) == 0)
#define LEQ(p1, p2) (!GT((p1), (p2)))
#define GEQ(p1, p2) (!LT((p1), (p2)))
struct device *get_highest_priority_device(struct class *class)
{
/* Caller is responsible for calling put_device */
struct class_dev_iter iter;
struct device *dev;
struct device *hpdev;
class_dev_iter_init(&iter, class, NULL, NULL);
dev = class_dev_iter_next(&iter);
hpdev = get_device(dev);
while (dev) {
if (GT(to_ppd(dev), to_ppd(hpdev))) {
put_device(hpdev);
hpdev = get_device(dev);
}
dev = class_dev_iter_next(&iter);
}
class_dev_iter_exit(&iter);
return hpdev;
}
EXPORT_SYMBOL(get_highest_priority_device);
struct device *get_lowest_priority_device(struct class *class)
{
struct class_dev_iter iter;
struct device *dev;
struct device *lpdev;
class_dev_iter_init(&iter, class, NULL, NULL);
dev = class_dev_iter_next(&iter);
lpdev = get_device(dev);
while (dev) {
if (LT(to_ppd(dev), to_ppd(lpdev))) {
put_device(lpdev);
lpdev = get_device(dev);
}
dev = class_dev_iter_next(&iter);
}
class_dev_iter_exit(&iter);
return lpdev;
}
struct device *get_next_lowest_priority_device(struct class *class,
struct device *compare_dev)
{
struct class_dev_iter iter;
struct device *current_dev;
struct device *next_lowest_dev;
dev_dbg(compare_dev, "Next_lowest_priority_device, finding next dev after %d %d",
to_ppd(compare_dev)->id, to_ppd(compare_dev)->priority);
class_dev_iter_init(&iter, class, NULL, NULL);
current_dev = class_dev_iter_next(&iter);
next_lowest_dev = NULL;
while (current_dev) {
if (LT(to_ppd(current_dev), to_ppd(compare_dev))) {
if (!next_lowest_dev)
next_lowest_dev = current_dev;
if (GEQ(to_ppd(current_dev), to_ppd(next_lowest_dev))) {
next_lowest_dev = get_device(current_dev);
dev_dbg(next_lowest_dev, "is the next lowest priority device");
}
}
current_dev = class_dev_iter_next(&iter);
}
class_dev_iter_exit(&iter);
return next_lowest_dev;
}
EXPORT_SYMBOL(get_next_lowest_priority_device);
struct device *relink_priority_list(struct class *class)
{
struct device *headdev;
struct device *dev;
struct device *ndev;
headdev = get_highest_priority_device(class);
if (!headdev)
return NULL;
dev = headdev;
to_ppd(dev)->next_hp = NULL;
ndev = get_next_lowest_priority_device(class, dev);
while (ndev) {
to_ppd(dev)->next_lp = ndev;
to_ppd(ndev)->next_hp = dev;
dev = ndev;
ndev = get_next_lowest_priority_device(class, dev);
}
to_ppd(dev)->next_lp = NULL;
return headdev;
}
/* Sums the power used by pdev and all lower-priority devices. To get the total power used, set pdev to NULL */
static int determine_power_used(struct class *class, struct device *pdev)
{
int power_used = 0;
struct pse_port_device *port;
if (pdev == NULL)
pdev = get_highest_priority_device(class);
while (pdev) {
port = to_ppd(pdev);
power_used += port->power_allocation;
pdev = get_next_lowest_priority_device(class, &port->dev);
}
return power_used;
}
static int power_port(struct device *dev)
{
struct pse_data *pse_data = get_pse_data(dev->class);
struct pse_port_device *port = to_pse_port_device(dev);
uint16_t port_class = port->last_class_result;
if (port_class >= CLASS_INVALID) {
dev_warn(dev, "Attempt to power a port with an invalid class\n");
return -EINVAL;
}
if (port->power_allocation == 0)
port->ops->apply_power(&port->dev, 1);
/* New power used is the difference between what we were allocated, and what we're requesting */
pse_data->power_used += port->power_request - port->power_allocation;
port->power_allocation = port->power_request;
port->ops->set_power_limit(&port->dev, port->power_allocation);
return 0;
}
void disconnect_port(struct device *dev)
{
struct pse_port_device *port = to_pse_port_device(dev);
struct pse_data *pse_data = get_pse_data(dev->class);
if (port->power_allocation > 0)
pse_data->power_used -= port->power_allocation;
port->power_allocation = 0;
port->power_request = 0;
port->ops->reset(&port->dev);
}
/* request_power - potentially power a port. This function really needs to be
* called in port-priority order. If this is not called in priority order then
* when the get glitching, where a lower-priority port will be powered briefly
* until the higher-priority port port requesting the power has a higher
* priority than ports that are currently powered, those will likely be
* unpowered. NOTE: power_request should be a positive number indicating total
* power wanted.
*/
static int request_power(struct device *dev)
{
int power_remaining;
int lpu = 0;
int32_t power_request;
struct device *lpdev;
struct pse_data *pse_data = get_pse_data(dev->class);
struct pse_port_device *port = to_pse_port_device(dev);
dev_dbg(&port->dev, "Request_power begin:");
if (port->last_class_result >= CLASS_INVALID) {
dev_warn(&port->dev, "request_power called with invalid port classification result");
port->ops->reset(&port->dev);
return -EINVAL;
}
power_request = port->power_request - port->power_allocation;
power_remaining = ((int32_t) pse_data->power_budget) - ((int32_t) pse_data->power_used);
dev_dbg(&port->dev, "Is requesting %i mW", power_request);
dev_dbg(&port->dev, "We have %i mW remaining in our budget", power_remaining);
if (power_remaining >= power_request) {
dev_info(&port->dev, "Powering port%d because: power_budget (%i) - power_used (%i) - power_requested (%i) >= 0", port->id, pse_data->power_budget, pse_data->power_used, power_request);
return power_port(&port->dev);
}
/* If the total current potentially gained by unpowering all the ports _in
* addition to the power currently remaining_ will not give enough current
* to power this device, there's no point to unpowering all the other ports
*/
lpu = determine_power_used(&pse_data->cls, &port->dev);
if ((power_remaining + lpu) < power_request) {
dev_dbg(&port->dev, " No budget left, power used by lower-priority devices is %i, power requested is %i", lpu, power_request);
return -EBUSY;
}
dev_info(&port->dev, "power_remaining (%i) + power used by lower priority devices (%i) will net us %i enough to power this request of %i", power_remaining, lpu, power_remaining+lpu, power_request);
lpdev = get_lowest_priority_device(&pse_data->cls);
while (lpdev && (power_remaining < power_request)) {
if (to_ppd(lpdev)->power_allocation > 0) {
dev_info(&port->dev, "port%d (priority %d) is being unpowered to power port%d (priority %d)", to_ppd(lpdev)->id, to_ppd(lpdev)->priority, port->id, port->priority);
disconnect_port(lpdev);
power_remaining = pse_data->power_budget - pse_data->power_used;
}
lpdev = to_ppd(lpdev)->next_hp;
}
if (power_remaining >= power_request) {
dev_info(&port->dev, "Powering port%d, after powering off lower priority ports\n", port->id);
return power_port(&port->dev);
}
/* If we get here, then our algorithm is wrong or state is corrupt */
dev_info(&port->dev, "Warning, power_remaining (%i) < power_request (%i), even after unpowering devices", power_remaining, power_request);
return -EAGAIN;
}
/* This is called in response to userspace writing to pse/port_priority, or
* pse/power_budget
*/
void redistribute_power(struct class *class)
{
struct device *h;
struct pse_port_device *p;
h = get_highest_priority_device(class);
while (h) {
p = to_ppd(h);
mutex_lock(&p->lock);
queue_work(p->wq, &p->disconnect);
mutex_unlock(&p->lock);
h = get_next_lowest_priority_device(class, h);
}
}
static void pse_port_power_request_handler(struct work_struct *work)
{
struct pse_port_device *port = container_of(work, struct pse_port_device, request_power);
struct pse_data *pse_data = get_pse_data(port->dev.class);
mutex_lock(&pse_data->pse_lock);
dev_dbg(&port->dev, "in %s\n", __func__);
if (!pse_data->enabled) {
/* No powering devices if we're disabled */
goto unlock_class_exit;
}
mutex_lock(&port->lock);
if (!port->enabled) {
/* No powering devices if the port is disabled */
goto unlock_port_exit;
}
if (port->power_request == port->power_allocation) {
/* If the port is already powered, don't try to power it again*/
goto unlock_port_exit;
}
if (port->last_class_result != CLASS_INVALID && port->last_detect_result == DETECT_POWERED_DEVICE) {
if (request_power(&port->dev) == 0)
dev_info(&port->dev, "Has been allocated %i mW, and will be powered", port->power_allocation);
}
unlock_port_exit:
mutex_unlock(&port->lock);
unlock_class_exit:
mutex_unlock(&pse_data->pse_lock);
}
static void pse_port_power_disconnect_handler(struct work_struct *work)
{
struct pse_port_device *p = container_of(work, struct pse_port_device, disconnect);
struct pse_data *pse_data = get_pse_data(p->dev.class);
mutex_lock(&pse_data->pse_lock);
disconnect_port(&p->dev);
mutex_unlock(&pse_data->pse_lock);
}
static void pse_port_device_release(struct device *dev)
{
struct pse_port_device *port = to_ppd(dev);
struct pse_data *pse_data = get_pse_data(dev->class);
mutex_lock(&pse_data->pse_lock);
dev_dbg(dev, "%s:%d\n", __func__, __LINE__);
ida_simple_remove(&pse_port_ida, port->id);
kfree(port);
mutex_unlock(&pse_data->pse_lock);
}
struct pse_port_device *pse_port_device_create(struct device *dev, const char *name, const struct pse_port_ops *ops, struct module *owner)
{
struct pse_port_device *pse_port;
pse_port = devm_kzalloc(dev, sizeof(struct pse_port_device), GFP_KERNEL);
if (pse_port == NULL)
return NULL;
pse_port->ops = ops;
pse_port->owner = owner;
pse_port->dev.parent = dev;
strlcpy(pse_port->name, name, PSE_PORT_NAME_SIZE);
return pse_port_device_register(pse_port);
}
EXPORT_SYMBOL(pse_port_device_create);
struct pse_port_device *pse_port_device_register(struct pse_port_device *pse_port)
{
int err;
int id = -1;
struct pse_data *pse_data = get_pse_data(g_pse_class);
if (id < 0) {
id = ida_simple_get(&pse_port_ida, 0, 0, GFP_KERNEL);
if (id < 0) {
err = id;
goto exit;
}
}
if (id >= PSE_MAX_PORTS) {
dev_warn(&pse_port->dev, "Max number of PSE ports (%d) have been registered", PSE_MAX_PORTS);
err = -ENOMEM;
goto exit_ida;
}
pse_port->id = id;
pse_port->enabled = 0;
pse_port->power_allocation = 0;
pse_port->priority = pse_data->port_priorities[id];
pse_port->last_class_result = CLASS_INVALID;
pse_port->last_detect_result = DETECT_NO_DEVICE;
pse_port->dev.class = g_pse_class;
pse_port->dev.release = pse_port_device_release;
dev_set_name(&pse_port->dev, "port%d", id);
pse_port->wq = alloc_ordered_workqueue("pse-port%d-wq", 0, pse_port->id);
mutex_init(&pse_port->lock);
INIT_WORK(&pse_port->request_power, pse_port_power_request_handler);
INIT_WORK(&pse_port->disconnect, pse_port_power_disconnect_handler);
err = device_register(&pse_port->dev);
if (err) {
put_device(&pse_port->dev);
goto unlock_exit_ida;
}
mutex_lock(&pse_data->pse_lock);
relink_priority_list(&pse_data->cls);
mutex_unlock(&pse_data->pse_lock);
redistribute_power(&pse_data->cls);
dev_info(&pse_port->dev, "pse core: registered port %d as %s with priority %d\n", pse_port->id, dev_name(&pse_port->dev), pse_port->priority);
return pse_port;
unlock_exit_ida:
mutex_unlock(&pse_data->pse_lock);
exit_ida:
ida_simple_remove(&pse_port_ida, id);
exit:
dev_err(&pse_port->dev, "pse core: unable to register port %d, err = %d\n", id, err);
return ERR_PTR(err);
}
EXPORT_SYMBOL(pse_port_device_register);
/* pse_port_device_unregister - unregister a pse_port_device
*
* @port: the pse port class device to destroy
*/
void pse_port_device_unregister(struct pse_port_device *port)
{
int id;
if (get_device(&port->dev) != NULL) {
id = port->id;
dev_info(&port->dev, "Unregistering port%d\n", id);
device_unregister(&port->dev);
ida_simple_remove(&pse_port_ida, id);
put_device(&port->dev);
}
}
EXPORT_SYMBOL(pse_port_device_unregister);
static int __init pse_init(void)
{
int i = 0;
int err = 0;
struct pse_data *pse_data = kzalloc(sizeof(struct pse_data), GFP_KERNEL);
if (!pse_data)
return -ENOMEM;
/* Since kzalloc will zero out the member before returning
* the pointer, we need to set every potential port's priority
* to the lowest possible (highest numerical value)
*/
for (i = 0; i < PSE_MAX_PORTS; i++)
pse_data->port_priorities[i] = 0xFF;
mutex_init(&pse_data->pse_lock);
pse_data->enabled = 0;
pse_data->power_used = 0;
pse_data->power_budget = 0;
/* Now we should set the various class things and such */
pse_data->cls.name = PSE_CLASS_NAME_STR;
pse_data->cls.owner = THIS_MODULE;
pse_data->cls.dev_groups = pse_port_dev_attr_groups;
/* With necessary fields filled out, lets register the pse class */
err = class_register(&pse_data->cls);
if (err) {
pr_info("Failed to register the PSE class");
kfree(pse_data);
return err;
}
/* We can now save the pse_data pointer */
g_pse_class = &pse_data->cls;
/* Now we should create the sysfs files for enable, power_budget,ect */
pse_class_sysfs_init(&pse_data->cls);
return err;
}
static void __exit pse_exit(void)
{
struct pse_data *pse_data = get_pse_data(g_pse_class);
pse_class_sysfs_close(&pse_data->cls);
mutex_destroy(&pse_data->pse_lock);
class_destroy(&pse_data->cls);
ida_destroy(&pse_port_ida);
kfree(pse_data);
}
subsys_initcall(pse_init);
module_exit(pse_exit);
MODULE_AUTHOR("Kyle Swenson <kswenson@cradlepoint.com>");
MODULE_DESCRIPTION("PoE (PSE) sysfs class");
MODULE_LICENSE("GPL");