linux/drivers/hwmon/occ/p9_sbe.c
Uwe Kleine-König ade539199b hwmon: (occ/p9_sbe) Convert to platform remove callback returning void
The .remove() callback for a platform driver returns an int which makes
many driver authors wrongly assume it's possible to do error handling by
returning an error code. However the value returned is ignored (apart
from emitting a warning) and this typically results in resource leaks.
To improve here there is a quest to make the remove callback return
void. In the first step of this quest all drivers are converted to
.remove_new() which already returns void. Eventually after all drivers
are converted, .remove_new() is renamed to .remove().

Trivially convert this driver from always returning zero in the remove
callback to the void returning variant.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Link: https://lore.kernel.org/r/20230918085951.1234172-12-u.kleine-koenig@pengutronix.de
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
2023-10-27 07:27:24 -07:00

204 lines
4.4 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/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include "common.h"
#define OCC_CHECKSUM_RETRIES 3
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) {
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,
void *resp, size_t resp_len)
{
size_t original_resp_len = resp_len;
struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
int rc, i;
for (i = 0; i < OCC_CHECKSUM_RETRIES; ++i) {
rc = fsi_occ_submit(ctx->sbe, cmd, len, resp, &resp_len);
if (rc >= 0)
break;
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;
}
if (rc != -EBADE)
return rc;
resp_len = original_resp_len;
}
switch (((struct occ_response *)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 void 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);
kvfree(ctx->ffdc);
}
static const struct of_device_id p9_sbe_occ_of_match[] = {
{ .compatible = "ibm,p9-occ-hwmon" },
{ .compatible = "ibm,p10-occ-hwmon" },
{}
};
MODULE_DEVICE_TABLE(of, p9_sbe_occ_of_match);
static struct platform_driver p9_sbe_occ_driver = {
.driver = {
.name = "occ-hwmon",
.of_match_table = p9_sbe_occ_of_match,
},
.probe = p9_sbe_occ_probe,
.remove_new = 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");