1// SPDX-License-Identifier: GPL-2.0+
2// Copyright IBM Corp 2019
3
4#include <linux/device.h>
5#include <linux/errno.h>
6#include <linux/slab.h>
7#include <linux/fsi-occ.h>
8#include <linux/mm.h>
9#include <linux/module.h>
10#include <linux/mod_devicetable.h>
11#include <linux/mutex.h>
12#include <linux/platform_device.h>
13#include <linux/string.h>
14#include <linux/sysfs.h>
15
16#include "common.h"
17
18#define OCC_CHECKSUM_RETRIES 3
19
20struct p9_sbe_occ {
21 struct occ occ;
22 bool sbe_error;
23 void *ffdc;
24 size_t ffdc_len;
25 size_t ffdc_size;
26 struct mutex sbe_error_lock; /* lock access to ffdc data */
27 struct device *sbe;
28};
29
30#define to_p9_sbe_occ(x) container_of((x), struct p9_sbe_occ, occ)
31
32static ssize_t ffdc_read(struct file *filp, struct kobject *kobj,
33 struct bin_attribute *battr, char *buf, loff_t pos,
34 size_t count)
35{
36 ssize_t rc = 0;
37 struct occ *occ = dev_get_drvdata(kobj_to_dev(kobj));
38 struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
39
40 mutex_lock(&ctx->sbe_error_lock);
41 if (ctx->sbe_error) {
42 rc = memory_read_from_buffer(to: buf, count, ppos: &pos, from: ctx->ffdc,
43 available: ctx->ffdc_len);
44 if (pos >= ctx->ffdc_len)
45 ctx->sbe_error = false;
46 }
47 mutex_unlock(lock: &ctx->sbe_error_lock);
48
49 return rc;
50}
51static BIN_ATTR_RO(ffdc, OCC_MAX_RESP_WORDS * 4);
52
53static bool p9_sbe_occ_save_ffdc(struct p9_sbe_occ *ctx, const void *resp,
54 size_t resp_len)
55{
56 bool notify = false;
57
58 mutex_lock(&ctx->sbe_error_lock);
59 if (!ctx->sbe_error) {
60 if (resp_len > ctx->ffdc_size) {
61 kvfree(addr: ctx->ffdc);
62 ctx->ffdc = kvmalloc(size: resp_len, GFP_KERNEL);
63 if (!ctx->ffdc) {
64 ctx->ffdc_len = 0;
65 ctx->ffdc_size = 0;
66 goto done;
67 }
68
69 ctx->ffdc_size = resp_len;
70 }
71
72 notify = true;
73 ctx->sbe_error = true;
74 ctx->ffdc_len = resp_len;
75 memcpy(ctx->ffdc, resp, resp_len);
76 }
77
78done:
79 mutex_unlock(lock: &ctx->sbe_error_lock);
80 return notify;
81}
82
83static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len,
84 void *resp, size_t resp_len)
85{
86 size_t original_resp_len = resp_len;
87 struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
88 int rc, i;
89
90 for (i = 0; i < OCC_CHECKSUM_RETRIES; ++i) {
91 rc = fsi_occ_submit(dev: ctx->sbe, request: cmd, req_len: len, response: resp, resp_len: &resp_len);
92 if (rc >= 0)
93 break;
94 if (resp_len) {
95 if (p9_sbe_occ_save_ffdc(ctx, resp, resp_len))
96 sysfs_notify(kobj: &occ->bus_dev->kobj, NULL,
97 attr: bin_attr_ffdc.attr.name);
98 return rc;
99 }
100 if (rc != -EBADE)
101 return rc;
102 resp_len = original_resp_len;
103 }
104
105 switch (((struct occ_response *)resp)->return_status) {
106 case OCC_RESP_CMD_IN_PRG:
107 rc = -ETIMEDOUT;
108 break;
109 case OCC_RESP_SUCCESS:
110 rc = 0;
111 break;
112 case OCC_RESP_CMD_INVAL:
113 case OCC_RESP_CMD_LEN_INVAL:
114 case OCC_RESP_DATA_INVAL:
115 case OCC_RESP_CHKSUM_ERR:
116 rc = -EINVAL;
117 break;
118 case OCC_RESP_INT_ERR:
119 case OCC_RESP_BAD_STATE:
120 case OCC_RESP_CRIT_EXCEPT:
121 case OCC_RESP_CRIT_INIT:
122 case OCC_RESP_CRIT_WATCHDOG:
123 case OCC_RESP_CRIT_OCB:
124 case OCC_RESP_CRIT_HW:
125 rc = -EREMOTEIO;
126 break;
127 default:
128 rc = -EPROTO;
129 }
130
131 return rc;
132}
133
134static int p9_sbe_occ_probe(struct platform_device *pdev)
135{
136 int rc;
137 struct occ *occ;
138 struct p9_sbe_occ *ctx = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ctx),
139 GFP_KERNEL);
140 if (!ctx)
141 return -ENOMEM;
142
143 mutex_init(&ctx->sbe_error_lock);
144
145 ctx->sbe = pdev->dev.parent;
146 occ = &ctx->occ;
147 occ->bus_dev = &pdev->dev;
148 platform_set_drvdata(pdev, data: occ);
149
150 occ->powr_sample_time_us = 500;
151 occ->poll_cmd_data = 0x20; /* P9 OCC poll data */
152 occ->send_cmd = p9_sbe_occ_send_cmd;
153
154 rc = occ_setup(occ);
155 if (rc == -ESHUTDOWN)
156 rc = -ENODEV; /* Host is shutdown, don't spew errors */
157
158 if (!rc) {
159 rc = device_create_bin_file(dev: occ->bus_dev, attr: &bin_attr_ffdc);
160 if (rc) {
161 dev_warn(occ->bus_dev,
162 "failed to create SBE error ffdc file\n");
163 rc = 0;
164 }
165 }
166
167 return rc;
168}
169
170static void p9_sbe_occ_remove(struct platform_device *pdev)
171{
172 struct occ *occ = platform_get_drvdata(pdev);
173 struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
174
175 device_remove_bin_file(dev: occ->bus_dev, attr: &bin_attr_ffdc);
176
177 ctx->sbe = NULL;
178 occ_shutdown(occ);
179
180 kvfree(addr: ctx->ffdc);
181}
182
183static const struct of_device_id p9_sbe_occ_of_match[] = {
184 { .compatible = "ibm,p9-occ-hwmon" },
185 { .compatible = "ibm,p10-occ-hwmon" },
186 {}
187};
188MODULE_DEVICE_TABLE(of, p9_sbe_occ_of_match);
189
190static struct platform_driver p9_sbe_occ_driver = {
191 .driver = {
192 .name = "occ-hwmon",
193 .of_match_table = p9_sbe_occ_of_match,
194 },
195 .probe = p9_sbe_occ_probe,
196 .remove_new = p9_sbe_occ_remove,
197};
198
199module_platform_driver(p9_sbe_occ_driver);
200
201MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
202MODULE_DESCRIPTION("BMC P9 OCC hwmon driver");
203MODULE_LICENSE("GPL");
204

source code of linux/drivers/hwmon/occ/p9_sbe.c