aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEddie James <eajames@linux.vnet.ibm.com>2018-11-08 16:05:21 -0500
committerGuenter Roeck <linux@roeck-us.net>2018-12-02 19:25:28 -0500
commit7ed98dddb764eebf2783881a17dc4980181a6e1a (patch)
tree10c7ed7ed71f7f0b07172aa7661712932eb3b13c
parent6021c48f3acd68301eacd99ff5dd4744f7b2f288 (diff)
fsi: Add On-Chip Controller (OCC) driver
The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system. This driver provides an atomic communications channel between a service processor (e.g. a BMC) and the OCC. The driver is dependent on the FSI SBEFIFO driver to get hardware access through the SBE to the OCC SRAM. Commands are issued to the SBE to send or fetch data to the SRAM. Signed-off-by: Eddie James <eajames@linux.ibm.com> Signed-off-by: Andrew Jeffery <andrew@aj.id.au> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Joel Stanley <joel@jms.id.au> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
-rw-r--r--drivers/fsi/Kconfig10
-rw-r--r--drivers/fsi/Makefile1
-rw-r--r--drivers/fsi/fsi-occ.c599
-rw-r--r--include/linux/fsi-occ.h25
4 files changed, 635 insertions, 0 deletions
diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
index 99c99a5d57fe..5cc20f3c3fd6 100644
--- a/drivers/fsi/Kconfig
+++ b/drivers/fsi/Kconfig
@@ -65,4 +65,14 @@ config FSI_SBEFIFO
65 a pipe-like FSI device for communicating with the self boot engine 65 a pipe-like FSI device for communicating with the self boot engine
66 (SBE) on POWER processors. 66 (SBE) on POWER processors.
67 67
68config FSI_OCC
69 tristate "OCC SBEFIFO client device driver"
70 depends on FSI_SBEFIFO
71 ---help---
72 This option enables an SBEFIFO based On-Chip Controller (OCC) device
73 driver. The OCC is a device embedded on a POWER processor that collects
74 and aggregates sensor data from the processor and system. The OCC can
75 provide the raw sensor data as well as perform thermal and power
76 management on the system.
77
68endif 78endif
diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
index a50d6ce22fb3..62687ec86d2e 100644
--- a/drivers/fsi/Makefile
+++ b/drivers/fsi/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
5obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o 5obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o
6obj-$(CONFIG_FSI_SCOM) += fsi-scom.o 6obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
7obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o 7obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o
8obj-$(CONFIG_FSI_OCC) += fsi-occ.o
diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c
new file mode 100644
index 000000000000..a2301cea1cbb
--- /dev/null
+++ b/drivers/fsi/fsi-occ.c
@@ -0,0 +1,599 @@
1// SPDX-License-Identifier: GPL-2.0
2
3#include <linux/device.h>
4#include <linux/err.h>
5#include <linux/errno.h>
6#include <linux/fs.h>
7#include <linux/fsi-sbefifo.h>
8#include <linux/gfp.h>
9#include <linux/idr.h>
10#include <linux/kernel.h>
11#include <linux/list.h>
12#include <linux/miscdevice.h>
13#include <linux/module.h>
14#include <linux/mutex.h>
15#include <linux/fsi-occ.h>
16#include <linux/of.h>
17#include <linux/platform_device.h>
18#include <linux/sched.h>
19#include <linux/slab.h>
20#include <linux/uaccess.h>
21#include <asm/unaligned.h>
22
23#define OCC_SRAM_BYTES 4096
24#define OCC_CMD_DATA_BYTES 4090
25#define OCC_RESP_DATA_BYTES 4089
26
27#define OCC_SRAM_CMD_ADDR 0xFFFBE000
28#define OCC_SRAM_RSP_ADDR 0xFFFBF000
29
30/*
31 * Assume we don't have much FFDC, if we do we'll overflow and
32 * fail the command. This needs to be big enough for simple
33 * commands as well.
34 */
35#define OCC_SBE_STATUS_WORDS 32
36
37#define OCC_TIMEOUT_MS 1000
38#define OCC_CMD_IN_PRG_WAIT_MS 50
39
40struct occ {
41 struct device *dev;
42 struct device *sbefifo;
43 char name[32];
44 int idx;
45 struct miscdevice mdev;
46 struct mutex occ_lock;
47};
48
49#define to_occ(x) container_of((x), struct occ, mdev)
50
51struct occ_response {
52 u8 seq_no;
53 u8 cmd_type;
54 u8 return_status;
55 __be16 data_length;
56 u8 data[OCC_RESP_DATA_BYTES + 2]; /* two bytes checksum */
57} __packed;
58
59struct occ_client {
60 struct occ *occ;
61 struct mutex lock;
62 size_t data_size;
63 size_t read_offset;
64 u8 *buffer;
65};
66
67#define to_client(x) container_of((x), struct occ_client, xfr)
68
69static DEFINE_IDA(occ_ida);
70
71static int occ_open(struct inode *inode, struct file *file)
72{
73 struct occ_client *client = kzalloc(sizeof(*client), GFP_KERNEL);
74 struct miscdevice *mdev = file->private_data;
75 struct occ *occ = to_occ(mdev);
76
77 if (!client)
78 return -ENOMEM;
79
80 client->buffer = (u8 *)__get_free_page(GFP_KERNEL);
81 if (!client->buffer) {
82 kfree(client);
83 return -ENOMEM;
84 }
85
86 client->occ = occ;
87 mutex_init(&client->lock);
88 file->private_data = client;
89
90 /* We allocate a 1-page buffer, make sure it all fits */
91 BUILD_BUG_ON((OCC_CMD_DATA_BYTES + 3) > PAGE_SIZE);
92 BUILD_BUG_ON((OCC_RESP_DATA_BYTES + 7) > PAGE_SIZE);
93
94 return 0;
95}
96
97static ssize_t occ_read(struct file *file, char __user *buf, size_t len,
98 loff_t *offset)
99{
100 struct occ_client *client = file->private_data;
101 ssize_t rc = 0;
102
103 if (!client)
104 return -ENODEV;
105
106 if (len > OCC_SRAM_BYTES)
107 return -EINVAL;
108
109 mutex_lock(&client->lock);
110
111 /* This should not be possible ... */
112 if (WARN_ON_ONCE(client->read_offset > client->data_size)) {
113 rc = -EIO;
114 goto done;
115 }
116
117 /* Grab how much data we have to read */
118 rc = min(len, client->data_size - client->read_offset);
119 if (copy_to_user(buf, client->buffer + client->read_offset, rc))
120 rc = -EFAULT;
121 else
122 client->read_offset += rc;
123
124 done:
125 mutex_unlock(&client->lock);
126
127 return rc;
128}
129
130static ssize_t occ_write(struct file *file, const char __user *buf,
131 size_t len, loff_t *offset)
132{
133 struct occ_client *client = file->private_data;
134 size_t rlen, data_length;
135 u16 checksum = 0;
136 ssize_t rc, i;
137 u8 *cmd;
138
139 if (!client)
140 return -ENODEV;
141
142 if (len > (OCC_CMD_DATA_BYTES + 3) || len < 3)
143 return -EINVAL;
144
145 mutex_lock(&client->lock);
146
147 /* Construct the command */
148 cmd = client->buffer;
149
150 /* Sequence number (we could increment and compare with response) */
151 cmd[0] = 1;
152
153 /*
154 * Copy the user command (assume user data follows the occ command
155 * format)
156 * byte 0: command type
157 * bytes 1-2: data length (msb first)
158 * bytes 3-n: data
159 */
160 if (copy_from_user(&cmd[1], buf, len)) {
161 rc = -EFAULT;
162 goto done;
163 }
164
165 /* Extract data length */
166 data_length = (cmd[2] << 8) + cmd[3];
167 if (data_length > OCC_CMD_DATA_BYTES) {
168 rc = -EINVAL;
169 goto done;
170 }
171
172 /* Calculate checksum */
173 for (i = 0; i < data_length + 4; ++i)
174 checksum += cmd[i];
175
176 cmd[data_length + 4] = checksum >> 8;
177 cmd[data_length + 5] = checksum & 0xFF;
178
179 /* Submit command */
180 rlen = PAGE_SIZE;
181 rc = fsi_occ_submit(client->occ->dev, cmd, data_length + 6, cmd,
182 &rlen);
183 if (rc)
184 goto done;
185
186 /* Set read tracking data */
187 client->data_size = rlen;
188 client->read_offset = 0;
189
190 /* Done */
191 rc = len;
192
193 done:
194 mutex_unlock(&client->lock);
195
196 return rc;
197}
198
199static int occ_release(struct inode *inode, struct file *file)
200{
201 struct occ_client *client = file->private_data;
202
203 free_page((unsigned long)client->buffer);
204 kfree(client);
205
206 return 0;
207}
208
209static const struct file_operations occ_fops = {
210 .owner = THIS_MODULE,
211 .open = occ_open,
212 .read = occ_read,
213 .write = occ_write,
214 .release = occ_release,
215};
216
217static int occ_verify_checksum(struct occ_response *resp, u16 data_length)
218{
219 /* Fetch the two bytes after the data for the checksum. */
220 u16 checksum_resp = get_unaligned_be16(&resp->data[data_length]);
221 u16 checksum;
222 u16 i;
223
224 checksum = resp->seq_no;
225 checksum += resp->cmd_type;
226 checksum += resp->return_status;
227 checksum += (data_length >> 8) + (data_length & 0xFF);
228
229 for (i = 0; i < data_length; ++i)
230 checksum += resp->data[i];
231
232 if (checksum != checksum_resp)
233 return -EBADMSG;
234
235 return 0;
236}
237
238static int occ_getsram(struct occ *occ, u32 address, void *data, ssize_t len)
239{
240 u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */
241 size_t resp_len, resp_data_len;
242 __be32 *resp, cmd[5];
243 int rc;
244
245 /*
246 * Magic sequence to do SBE getsram command. SBE will fetch data from
247 * specified SRAM address.
248 */
249 cmd[0] = cpu_to_be32(0x5);
250 cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_OCC_SRAM);
251 cmd[2] = cpu_to_be32(1);
252 cmd[3] = cpu_to_be32(address);
253 cmd[4] = cpu_to_be32(data_len);
254
255 resp_len = (data_len >> 2) + OCC_SBE_STATUS_WORDS;
256 resp = kzalloc(resp_len << 2, GFP_KERNEL);
257 if (!resp)
258 return -ENOMEM;
259
260 rc = sbefifo_submit(occ->sbefifo, cmd, 5, resp, &resp_len);
261 if (rc)
262 goto free;
263
264 rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_GET_OCC_SRAM,
265 resp, resp_len, &resp_len);
266 if (rc)
267 goto free;
268
269 resp_data_len = be32_to_cpu(resp[resp_len - 1]);
270 if (resp_data_len != data_len) {
271 dev_err(occ->dev, "SRAM read expected %d bytes got %zd\n",
272 data_len, resp_data_len);
273 rc = -EBADMSG;
274 } else {
275 memcpy(data, resp, len);
276 }
277
278free:
279 /* Convert positive SBEI status */
280 if (rc > 0) {
281 dev_err(occ->dev, "SRAM read returned failure status: %08x\n",
282 rc);
283 rc = -EBADMSG;
284 }
285
286 kfree(resp);
287 return rc;
288}
289
290static int occ_putsram(struct occ *occ, u32 address, const void *data,
291 ssize_t len)
292{
293 size_t cmd_len, buf_len, resp_len, resp_data_len;
294 u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */
295 __be32 *buf;
296 int rc;
297
298 /*
299 * We use the same buffer for command and response, make
300 * sure it's big enough
301 */
302 resp_len = OCC_SBE_STATUS_WORDS;
303 cmd_len = (data_len >> 2) + 5;
304 buf_len = max(cmd_len, resp_len);
305 buf = kzalloc(buf_len << 2, GFP_KERNEL);
306 if (!buf)
307 return -ENOMEM;
308
309 /*
310 * Magic sequence to do SBE putsram command. SBE will transfer
311 * data to specified SRAM address.
312 */
313 buf[0] = cpu_to_be32(cmd_len);
314 buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
315 buf[2] = cpu_to_be32(1);
316 buf[3] = cpu_to_be32(address);
317 buf[4] = cpu_to_be32(data_len);
318
319 memcpy(&buf[5], data, len);
320
321 rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
322 if (rc)
323 goto free;
324
325 rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM,
326 buf, resp_len, &resp_len);
327 if (rc)
328 goto free;
329
330 if (resp_len != 1) {
331 dev_err(occ->dev, "SRAM write response length invalid: %zd\n",
332 resp_len);
333 rc = -EBADMSG;
334 } else {
335 resp_data_len = be32_to_cpu(buf[0]);
336 if (resp_data_len != data_len) {
337 dev_err(occ->dev,
338 "SRAM write expected %d bytes got %zd\n",
339 data_len, resp_data_len);
340 rc = -EBADMSG;
341 }
342 }
343
344free:
345 /* Convert positive SBEI status */
346 if (rc > 0) {
347 dev_err(occ->dev, "SRAM write returned failure status: %08x\n",
348 rc);
349 rc = -EBADMSG;
350 }
351
352 kfree(buf);
353 return rc;
354}
355
356static int occ_trigger_attn(struct occ *occ)
357{
358 __be32 buf[OCC_SBE_STATUS_WORDS];
359 size_t resp_len, resp_data_len;
360 int rc;
361
362 BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 7);
363 resp_len = OCC_SBE_STATUS_WORDS;
364
365 buf[0] = cpu_to_be32(0x5 + 0x2); /* Chip-op length in words */
366 buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
367 buf[2] = cpu_to_be32(0x3); /* Mode: Circular */
368 buf[3] = cpu_to_be32(0x0); /* Address: ignore in mode 3 */
369 buf[4] = cpu_to_be32(0x8); /* Data length in bytes */
370 buf[5] = cpu_to_be32(0x20010000); /* Trigger OCC attention */
371 buf[6] = 0;
372
373 rc = sbefifo_submit(occ->sbefifo, buf, 7, buf, &resp_len);
374 if (rc)
375 goto error;
376
377 rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM,
378 buf, resp_len, &resp_len);
379 if (rc)
380 goto error;
381
382 if (resp_len != 1) {
383 dev_err(occ->dev, "SRAM attn response length invalid: %zd\n",
384 resp_len);
385 rc = -EBADMSG;
386 } else {
387 resp_data_len = be32_to_cpu(buf[0]);
388 if (resp_data_len != 8) {
389 dev_err(occ->dev,
390 "SRAM attn expected 8 bytes got %zd\n",
391 resp_data_len);
392 rc = -EBADMSG;
393 }
394 }
395
396 error:
397 /* Convert positive SBEI status */
398 if (rc > 0) {
399 dev_err(occ->dev, "SRAM attn returned failure status: %08x\n",
400 rc);
401 rc = -EBADMSG;
402 }
403
404 return rc;
405}
406
407int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
408 void *response, size_t *resp_len)
409{
410 const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS);
411 const unsigned long wait_time =
412 msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
413 struct occ *occ = dev_get_drvdata(dev);
414 struct occ_response *resp = response;
415 u16 resp_data_length;
416 unsigned long start;
417 int rc;
418
419 if (!occ)
420 return -ENODEV;
421
422 if (*resp_len < 7) {
423 dev_dbg(dev, "Bad resplen %zd\n", *resp_len);
424 return -EINVAL;
425 }
426
427 mutex_lock(&occ->occ_lock);
428
429 rc = occ_putsram(occ, OCC_SRAM_CMD_ADDR, request, req_len);
430 if (rc)
431 goto done;
432
433 rc = occ_trigger_attn(occ);
434 if (rc)
435 goto done;
436
437 /* Read occ response header */
438 start = jiffies;
439 do {
440 rc = occ_getsram(occ, OCC_SRAM_RSP_ADDR, resp, 8);
441 if (rc)
442 goto done;
443
444 if (resp->return_status == OCC_RESP_CMD_IN_PRG) {
445 rc = -ETIMEDOUT;
446
447 if (time_after(jiffies, start + timeout))
448 break;
449
450 set_current_state(TASK_UNINTERRUPTIBLE);
451 schedule_timeout(wait_time);
452 }
453 } while (rc);
454
455 /* Extract size of response data */
456 resp_data_length = get_unaligned_be16(&resp->data_length);
457
458 /* Message size is data length + 5 bytes header + 2 bytes checksum */
459 if ((resp_data_length + 7) > *resp_len) {
460 rc = -EMSGSIZE;
461 goto done;
462 }
463
464 dev_dbg(dev, "resp_status=%02x resp_data_len=%d\n",
465 resp->return_status, resp_data_length);
466
467 /* Grab the rest */
468 if (resp_data_length > 1) {
469 /* already got 3 bytes resp, also need 2 bytes checksum */
470 rc = occ_getsram(occ, OCC_SRAM_RSP_ADDR + 8,
471 &resp->data[3], resp_data_length - 1);
472 if (rc)
473 goto done;
474 }
475
476 *resp_len = resp_data_length + 7;
477 rc = occ_verify_checksum(resp, resp_data_length);
478
479 done:
480 mutex_unlock(&occ->occ_lock);
481
482 return rc;
483}
484EXPORT_SYMBOL_GPL(fsi_occ_submit);
485
486static int occ_unregister_child(struct device *dev, void *data)
487{
488 struct platform_device *hwmon_dev = to_platform_device(dev);
489
490 platform_device_unregister(hwmon_dev);
491
492 return 0;
493}
494
495static int occ_probe(struct platform_device *pdev)
496{
497 int rc;
498 u32 reg;
499 struct occ *occ;
500 struct platform_device *hwmon_dev;
501 struct device *dev = &pdev->dev;
502 struct platform_device_info hwmon_dev_info = {
503 .parent = dev,
504 .name = "occ-hwmon",
505 };
506
507 occ = devm_kzalloc(dev, sizeof(*occ), GFP_KERNEL);
508 if (!occ)
509 return -ENOMEM;
510
511 occ->dev = dev;
512 occ->sbefifo = dev->parent;
513 mutex_init(&occ->occ_lock);
514
515 if (dev->of_node) {
516 rc = of_property_read_u32(dev->of_node, "reg", &reg);
517 if (!rc) {
518 /* make sure we don't have a duplicate from dts */
519 occ->idx = ida_simple_get(&occ_ida, reg, reg + 1,
520 GFP_KERNEL);
521 if (occ->idx < 0)
522 occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX,
523 GFP_KERNEL);
524 } else {
525 occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX,
526 GFP_KERNEL);
527 }
528 } else {
529 occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, GFP_KERNEL);
530 }
531
532 platform_set_drvdata(pdev, occ);
533
534 snprintf(occ->name, sizeof(occ->name), "occ%d", occ->idx);
535 occ->mdev.fops = &occ_fops;
536 occ->mdev.minor = MISC_DYNAMIC_MINOR;
537 occ->mdev.name = occ->name;
538 occ->mdev.parent = dev;
539
540 rc = misc_register(&occ->mdev);
541 if (rc) {
542 dev_err(dev, "failed to register miscdevice: %d\n", rc);
543 ida_simple_remove(&occ_ida, occ->idx);
544 return rc;
545 }
546
547 hwmon_dev_info.id = occ->idx;
548 hwmon_dev = platform_device_register_full(&hwmon_dev_info);
549 if (!hwmon_dev)
550 dev_warn(dev, "failed to create hwmon device\n");
551
552 return 0;
553}
554
555static int occ_remove(struct platform_device *pdev)
556{
557 struct occ *occ = platform_get_drvdata(pdev);
558
559 misc_deregister(&occ->mdev);
560
561 device_for_each_child(&pdev->dev, NULL, occ_unregister_child);
562
563 ida_simple_remove(&occ_ida, occ->idx);
564
565 return 0;
566}
567
568static const struct of_device_id occ_match[] = {
569 { .compatible = "ibm,p9-occ" },
570 { },
571};
572
573static struct platform_driver occ_driver = {
574 .driver = {
575 .name = "occ",
576 .of_match_table = occ_match,
577 },
578 .probe = occ_probe,
579 .remove = occ_remove,
580};
581
582static int occ_init(void)
583{
584 return platform_driver_register(&occ_driver);
585}
586
587static void occ_exit(void)
588{
589 platform_driver_unregister(&occ_driver);
590
591 ida_destroy(&occ_ida);
592}
593
594module_init(occ_init);
595module_exit(occ_exit);
596
597MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
598MODULE_DESCRIPTION("BMC P9 OCC driver");
599MODULE_LICENSE("GPL");
diff --git a/include/linux/fsi-occ.h b/include/linux/fsi-occ.h
new file mode 100644
index 000000000000..d4cdc2aa6e33
--- /dev/null
+++ b/include/linux/fsi-occ.h
@@ -0,0 +1,25 @@
1// SPDX-License-Identifier: GPL-2.0
2
3#ifndef LINUX_FSI_OCC_H
4#define LINUX_FSI_OCC_H
5
6struct device;
7
8#define OCC_RESP_CMD_IN_PRG 0xFF
9#define OCC_RESP_SUCCESS 0
10#define OCC_RESP_CMD_INVAL 0x11
11#define OCC_RESP_CMD_LEN_INVAL 0x12
12#define OCC_RESP_DATA_INVAL 0x13
13#define OCC_RESP_CHKSUM_ERR 0x14
14#define OCC_RESP_INT_ERR 0x15
15#define OCC_RESP_BAD_STATE 0x16
16#define OCC_RESP_CRIT_EXCEPT 0xE0
17#define OCC_RESP_CRIT_INIT 0xE1
18#define OCC_RESP_CRIT_WATCHDOG 0xE2
19#define OCC_RESP_CRIT_OCB 0xE3
20#define OCC_RESP_CRIT_HW 0xE4
21
22int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
23 void *response, size_t *resp_len);
24
25#endif /* LINUX_FSI_OCC_H */