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 | |
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(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 | } |
51 | static BIN_ATTR_RO(ffdc, OCC_MAX_RESP_WORDS * 4); |
52 | |
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(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 | |
78 | done: |
79 | mutex_unlock(lock: &ctx->sbe_error_lock); |
80 | return notify; |
81 | } |
82 | |
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(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 | |
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(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 | |
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(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 | |
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 | |