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