linux/drivers/hwmon/occ/p9_sbe.c
Eddie James 849b0156d9 hwmon: (occ) Delay hwmon registration until user request
Instead of registering the hwmon device at probe time, use the
existing "occ_active" sysfs file to control when the driver polls
the OCC for sensor data and registers with hwmon. The reason for
this change is that the SBE, which is the device by which the
driver communicates with the OCC, cannot handle communications
during certain system state transitions, resulting in
unrecoverable system errors.

Signed-off-by: Eddie James <eajames@linux.ibm.com>
Link: https://lore.kernel.org/r/20220427140443.11428-1-eajames@linux.ibm.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2022-05-20 10:57:06 -07:00

193 lines
4.0 KiB
C

// SPDX-License-Identifier: GPL-2.0+
// Copyright IBM Corp 2019
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/fsi-occ.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include "common.h"
struct p9_sbe_occ {
struct occ occ;
bool sbe_error;
void *ffdc;
size_t ffdc_len;
size_t ffdc_size;
struct mutex sbe_error_lock; /* lock access to ffdc data */
struct device *sbe;
};
#define to_p9_sbe_occ(x) container_of((x), struct p9_sbe_occ, occ)
static ssize_t ffdc_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *battr, char *buf, loff_t pos,
size_t count)
{
ssize_t rc = 0;
struct occ *occ = dev_get_drvdata(kobj_to_dev(kobj));
struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
mutex_lock(&ctx->sbe_error_lock);
if (ctx->sbe_error) {
rc = memory_read_from_buffer(buf, count, &pos, ctx->ffdc,
ctx->ffdc_len);
if (pos >= ctx->ffdc_len)
ctx->sbe_error = false;
}
mutex_unlock(&ctx->sbe_error_lock);
return rc;
}
static BIN_ATTR_RO(ffdc, OCC_MAX_RESP_WORDS * 4);
static bool p9_sbe_occ_save_ffdc(struct p9_sbe_occ *ctx, const void *resp,
size_t resp_len)
{
bool notify = false;
mutex_lock(&ctx->sbe_error_lock);
if (!ctx->sbe_error) {
if (resp_len > ctx->ffdc_size) {
if (ctx->ffdc)
kvfree(ctx->ffdc);
ctx->ffdc = kvmalloc(resp_len, GFP_KERNEL);
if (!ctx->ffdc) {
ctx->ffdc_len = 0;
ctx->ffdc_size = 0;
goto done;
}
ctx->ffdc_size = resp_len;
}
notify = true;
ctx->sbe_error = true;
ctx->ffdc_len = resp_len;
memcpy(ctx->ffdc, resp, resp_len);
}
done:
mutex_unlock(&ctx->sbe_error_lock);
return notify;
}
static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len)
{
struct occ_response *resp = &occ->resp;
struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
size_t resp_len = sizeof(*resp);
int rc;
rc = fsi_occ_submit(ctx->sbe, cmd, len, resp, &resp_len);
if (rc < 0) {
if (resp_len) {
if (p9_sbe_occ_save_ffdc(ctx, resp, resp_len))
sysfs_notify(&occ->bus_dev->kobj, NULL,
bin_attr_ffdc.attr.name);
}
return rc;
}
switch (resp->return_status) {
case OCC_RESP_CMD_IN_PRG:
rc = -ETIMEDOUT;
break;
case OCC_RESP_SUCCESS:
rc = 0;
break;
case OCC_RESP_CMD_INVAL:
case OCC_RESP_CMD_LEN_INVAL:
case OCC_RESP_DATA_INVAL:
case OCC_RESP_CHKSUM_ERR:
rc = -EINVAL;
break;
case OCC_RESP_INT_ERR:
case OCC_RESP_BAD_STATE:
case OCC_RESP_CRIT_EXCEPT:
case OCC_RESP_CRIT_INIT:
case OCC_RESP_CRIT_WATCHDOG:
case OCC_RESP_CRIT_OCB:
case OCC_RESP_CRIT_HW:
rc = -EREMOTEIO;
break;
default:
rc = -EPROTO;
}
return rc;
}
static int p9_sbe_occ_probe(struct platform_device *pdev)
{
int rc;
struct occ *occ;
struct p9_sbe_occ *ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx),
GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mutex_init(&ctx->sbe_error_lock);
ctx->sbe = pdev->dev.parent;
occ = &ctx->occ;
occ->bus_dev = &pdev->dev;
platform_set_drvdata(pdev, occ);
occ->powr_sample_time_us = 500;
occ->poll_cmd_data = 0x20; /* P9 OCC poll data */
occ->send_cmd = p9_sbe_occ_send_cmd;
rc = occ_setup(occ);
if (rc == -ESHUTDOWN)
rc = -ENODEV; /* Host is shutdown, don't spew errors */
if (!rc) {
rc = device_create_bin_file(occ->bus_dev, &bin_attr_ffdc);
if (rc) {
dev_warn(occ->bus_dev,
"failed to create SBE error ffdc file\n");
rc = 0;
}
}
return rc;
}
static int p9_sbe_occ_remove(struct platform_device *pdev)
{
struct occ *occ = platform_get_drvdata(pdev);
struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
device_remove_bin_file(occ->bus_dev, &bin_attr_ffdc);
ctx->sbe = NULL;
occ_shutdown(occ);
if (ctx->ffdc)
kvfree(ctx->ffdc);
return 0;
}
static struct platform_driver p9_sbe_occ_driver = {
.driver = {
.name = "occ-hwmon",
},
.probe = p9_sbe_occ_probe,
.remove = p9_sbe_occ_remove,
};
module_platform_driver(p9_sbe_occ_driver);
MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
MODULE_DESCRIPTION("BMC P9 OCC hwmon driver");
MODULE_LICENSE("GPL");