xref: /linux/drivers/hwmon/occ/p9_sbe.c (revision 0ea5c948cb64bab5bc7a5516774eb8536f05aa0d)
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 
20 struct 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 
ffdc_read(struct file * filp,struct kobject * kobj,struct bin_attribute * battr,char * buf,loff_t pos,size_t count)32 static 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(buf, count, &pos, ctx->ffdc,
43 					     ctx->ffdc_len);
44 		if (pos >= ctx->ffdc_len)
45 			ctx->sbe_error = false;
46 	}
47 	mutex_unlock(&ctx->sbe_error_lock);
48 
49 	return rc;
50 }
51 static BIN_ATTR_RO(ffdc, OCC_MAX_RESP_WORDS * 4);
52 
p9_sbe_occ_save_ffdc(struct p9_sbe_occ * ctx,const void * resp,size_t resp_len)53 static 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(ctx->ffdc);
62 			ctx->ffdc = kvmalloc(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 
78 done:
79 	mutex_unlock(&ctx->sbe_error_lock);
80 	return notify;
81 }
82 
p9_sbe_occ_send_cmd(struct occ * occ,u8 * cmd,size_t len,void * resp,size_t resp_len)83 static 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(ctx->sbe, cmd, len, resp, &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(&occ->bus_dev->kobj, NULL,
97 					     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 
p9_sbe_occ_probe(struct platform_device * pdev)134 static 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(&pdev->dev, 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, 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(occ->bus_dev, &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 
p9_sbe_occ_remove(struct platform_device * pdev)170 static 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(occ->bus_dev, &bin_attr_ffdc);
176 
177 	ctx->sbe = NULL;
178 	occ_shutdown(occ);
179 
180 	kvfree(ctx->ffdc);
181 }
182 
183 static const struct of_device_id p9_sbe_occ_of_match[] = {
184 	{ .compatible = "ibm,p9-occ-hwmon" },
185 	{ .compatible = "ibm,p10-occ-hwmon" },
186 	{}
187 };
188 MODULE_DEVICE_TABLE(of, p9_sbe_occ_of_match);
189 
190 static 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 
199 module_platform_driver(p9_sbe_occ_driver);
200 
201 MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
202 MODULE_DESCRIPTION("BMC P9 OCC hwmon driver");
203 MODULE_LICENSE("GPL");
204