Merge tag 'hardening-v5.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git...
[sfrench/cifs-2.6.git] / drivers / hwmon / occ / p9_sbe.c
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
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
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                         kvfree(ctx->ffdc);
59                         ctx->ffdc = kvmalloc(resp_len, GFP_KERNEL);
60                         if (!ctx->ffdc) {
61                                 ctx->ffdc_len = 0;
62                                 ctx->ffdc_size = 0;
63                                 goto done;
64                         }
65
66                         ctx->ffdc_size = resp_len;
67                 }
68
69                 notify = true;
70                 ctx->sbe_error = true;
71                 ctx->ffdc_len = resp_len;
72                 memcpy(ctx->ffdc, resp, resp_len);
73         }
74
75 done:
76         mutex_unlock(&ctx->sbe_error_lock);
77         return notify;
78 }
79
80 static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd, size_t len,
81                                void *resp, size_t resp_len)
82 {
83         struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
84         int rc;
85
86         rc = fsi_occ_submit(ctx->sbe, cmd, len, resp, &resp_len);
87         if (rc < 0) {
88                 if (resp_len) {
89                         if (p9_sbe_occ_save_ffdc(ctx, resp, resp_len))
90                                 sysfs_notify(&occ->bus_dev->kobj, NULL,
91                                              bin_attr_ffdc.attr.name);
92                 }
93
94                 return rc;
95         }
96
97         switch (((struct occ_response *)resp)->return_status) {
98         case OCC_RESP_CMD_IN_PRG:
99                 rc = -ETIMEDOUT;
100                 break;
101         case OCC_RESP_SUCCESS:
102                 rc = 0;
103                 break;
104         case OCC_RESP_CMD_INVAL:
105         case OCC_RESP_CMD_LEN_INVAL:
106         case OCC_RESP_DATA_INVAL:
107         case OCC_RESP_CHKSUM_ERR:
108                 rc = -EINVAL;
109                 break;
110         case OCC_RESP_INT_ERR:
111         case OCC_RESP_BAD_STATE:
112         case OCC_RESP_CRIT_EXCEPT:
113         case OCC_RESP_CRIT_INIT:
114         case OCC_RESP_CRIT_WATCHDOG:
115         case OCC_RESP_CRIT_OCB:
116         case OCC_RESP_CRIT_HW:
117                 rc = -EREMOTEIO;
118                 break;
119         default:
120                 rc = -EPROTO;
121         }
122
123         return rc;
124 }
125
126 static int p9_sbe_occ_probe(struct platform_device *pdev)
127 {
128         int rc;
129         struct occ *occ;
130         struct p9_sbe_occ *ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx),
131                                               GFP_KERNEL);
132         if (!ctx)
133                 return -ENOMEM;
134
135         mutex_init(&ctx->sbe_error_lock);
136
137         ctx->sbe = pdev->dev.parent;
138         occ = &ctx->occ;
139         occ->bus_dev = &pdev->dev;
140         platform_set_drvdata(pdev, occ);
141
142         occ->powr_sample_time_us = 500;
143         occ->poll_cmd_data = 0x20;              /* P9 OCC poll data */
144         occ->send_cmd = p9_sbe_occ_send_cmd;
145
146         rc = occ_setup(occ);
147         if (rc == -ESHUTDOWN)
148                 rc = -ENODEV;   /* Host is shutdown, don't spew errors */
149
150         if (!rc) {
151                 rc = device_create_bin_file(occ->bus_dev, &bin_attr_ffdc);
152                 if (rc) {
153                         dev_warn(occ->bus_dev,
154                                  "failed to create SBE error ffdc file\n");
155                         rc = 0;
156                 }
157         }
158
159         return rc;
160 }
161
162 static int p9_sbe_occ_remove(struct platform_device *pdev)
163 {
164         struct occ *occ = platform_get_drvdata(pdev);
165         struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
166
167         device_remove_bin_file(occ->bus_dev, &bin_attr_ffdc);
168
169         ctx->sbe = NULL;
170         occ_shutdown(occ);
171
172         kvfree(ctx->ffdc);
173
174         return 0;
175 }
176
177 static struct platform_driver p9_sbe_occ_driver = {
178         .driver = {
179                 .name = "occ-hwmon",
180         },
181         .probe  = p9_sbe_occ_probe,
182         .remove = p9_sbe_occ_remove,
183 };
184
185 module_platform_driver(p9_sbe_occ_driver);
186
187 MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
188 MODULE_DESCRIPTION("BMC P9 OCC hwmon driver");
189 MODULE_LICENSE("GPL");