linux/drivers/peci/core.c
Iwona Winiarska 42bed52b2e peci: Add sysfs interface for PECI bus
PECI devices may not be discoverable at the time when PECI controller is
being added (e.g. BMC can boot up when the Host system is still in S5).
Since we currently don't have the capabilities to figure out the Host
system state inside the PECI subsystem itself, we have to rely on
userspace to do it for us.

In the future, PECI subsystem may be expanded with mechanisms that allow
us to avoid depending on userspace interaction (e.g. CPU presence could
be detected using GPIO, and the information on whether it's discoverable
could be obtained over IPMI).
Unfortunately, those methods may ultimately not be available (support
will vary from platform to platform), which means that we still need
platform independent method triggered by userspace.

Acked-by: Joel Stanley <joel@jms.id.au>
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Link: https://lore.kernel.org/r/20220208153639.255278-8-iwona.winiarska@intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-02-09 08:04:44 +01:00

193 lines
4.3 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2018-2021 Intel Corporation
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/peci.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/slab.h>
#include "internal.h"
static DEFINE_IDA(peci_controller_ida);
static void peci_controller_dev_release(struct device *dev)
{
struct peci_controller *controller = to_peci_controller(dev);
mutex_destroy(&controller->bus_lock);
ida_free(&peci_controller_ida, controller->id);
kfree(controller);
}
struct device_type peci_controller_type = {
.release = peci_controller_dev_release,
};
int peci_controller_scan_devices(struct peci_controller *controller)
{
int ret;
u8 addr;
for (addr = PECI_BASE_ADDR; addr < PECI_BASE_ADDR + PECI_DEVICE_NUM_MAX; addr++) {
ret = peci_device_create(controller, addr);
if (ret)
return ret;
}
return 0;
}
static struct peci_controller *peci_controller_alloc(struct device *dev,
struct peci_controller_ops *ops)
{
struct peci_controller *controller;
int ret;
if (!ops->xfer)
return ERR_PTR(-EINVAL);
controller = kzalloc(sizeof(*controller), GFP_KERNEL);
if (!controller)
return ERR_PTR(-ENOMEM);
ret = ida_alloc_max(&peci_controller_ida, U8_MAX, GFP_KERNEL);
if (ret < 0)
goto err;
controller->id = ret;
controller->ops = ops;
controller->dev.parent = dev;
controller->dev.bus = &peci_bus_type;
controller->dev.type = &peci_controller_type;
device_initialize(&controller->dev);
mutex_init(&controller->bus_lock);
return controller;
err:
kfree(controller);
return ERR_PTR(ret);
}
static int unregister_child(struct device *dev, void *dummy)
{
peci_device_destroy(to_peci_device(dev));
return 0;
}
static void unregister_controller(void *_controller)
{
struct peci_controller *controller = _controller;
/*
* Detach any active PECI devices. This can't fail, thus we do not
* check the returned value.
*/
device_for_each_child_reverse(&controller->dev, NULL, unregister_child);
device_unregister(&controller->dev);
fwnode_handle_put(controller->dev.fwnode);
pm_runtime_disable(&controller->dev);
}
/**
* devm_peci_controller_add() - add PECI controller
* @dev: device for devm operations
* @ops: pointer to controller specific methods
*
* In final stage of its probe(), peci_controller driver calls
* devm_peci_controller_add() to register itself with the PECI bus.
*
* Return: Pointer to the newly allocated controller or ERR_PTR() in case of failure.
*/
struct peci_controller *devm_peci_controller_add(struct device *dev,
struct peci_controller_ops *ops)
{
struct peci_controller *controller;
int ret;
controller = peci_controller_alloc(dev, ops);
if (IS_ERR(controller))
return controller;
ret = dev_set_name(&controller->dev, "peci-%d", controller->id);
if (ret)
goto err_put;
pm_runtime_no_callbacks(&controller->dev);
pm_suspend_ignore_children(&controller->dev, true);
pm_runtime_enable(&controller->dev);
device_set_node(&controller->dev, fwnode_handle_get(dev_fwnode(dev)));
ret = device_add(&controller->dev);
if (ret)
goto err_fwnode;
ret = devm_add_action_or_reset(dev, unregister_controller, controller);
if (ret)
return ERR_PTR(ret);
/*
* Ignoring retval since failures during scan are non-critical for
* controller itself.
*/
peci_controller_scan_devices(controller);
return controller;
err_fwnode:
fwnode_handle_put(controller->dev.fwnode);
pm_runtime_disable(&controller->dev);
err_put:
put_device(&controller->dev);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_NS_GPL(devm_peci_controller_add, PECI);
struct bus_type peci_bus_type = {
.name = "peci",
.bus_groups = peci_bus_groups,
};
static int __init peci_init(void)
{
int ret;
ret = bus_register(&peci_bus_type);
if (ret < 0) {
pr_err("peci: failed to register PECI bus type!\n");
return ret;
}
return 0;
}
module_init(peci_init);
static void __exit peci_exit(void)
{
bus_unregister(&peci_bus_type);
}
module_exit(peci_exit);
MODULE_AUTHOR("Jason M Bills <jason.m.bills@linux.intel.com>");
MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
MODULE_AUTHOR("Iwona Winiarska <iwona.winiarska@intel.com>");
MODULE_DESCRIPTION("PECI bus core module");
MODULE_LICENSE("GPL");