aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/occ/p9_sbe.c
blob: 42fc7b97bb34b55ed5dc4ea73e31ff8b2a7a1af4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// 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");