blob: 5c53dadc4d31d75203c83b4020c16737f33978e2 [file] [log] [blame]
Edward A. James7bd86372017-05-15 12:28:44 -05001From patchwork Fri May 12 19:38:19 2017
2Content-Type: text/plain; charset="utf-8"
3MIME-Version: 1.0
4Content-Transfer-Encoding: 7bit
5Subject: [linux,dev-4.10,2/3] drivers: fsi: sbefifo: Add OCC driver
6From: eajames@linux.vnet.ibm.com
7X-Patchwork-Id: 761838
8Message-Id: <1494617900-32369-3-git-send-email-eajames@linux.vnet.ibm.com>
9To: openbmc@lists.ozlabs.org
10Cc: "Edward A. James" <eajames@us.ibm.com>, bradleyb@fuzziesquirrel.com,
11 cbostic@linux.vnet.ibm.com
12Date: Fri, 12 May 2017 14:38:19 -0500
13
14From: "Edward A. James" <eajames@us.ibm.com>
15
16This driver provides an atomic communications channel between the OCC on
17the POWER9 processor and a service processor (a BMC). The driver is
18dependent on the FSI SBEIFO driver to get hardware access to the OCC
19SRAM.
20
21The format of the communication is a command followed by a response.
22Since the command and response must be performed atomically, the driver
23will perform this operations asynchronously. In this way, a write
24operation starts the command, and a read will gather the response data
25once it is complete.
26
27Signed-off-by: Edward A. James <eajames@us.ibm.com>
28---
29 drivers/fsi/Kconfig | 9 +
30 drivers/fsi/Makefile | 1 +
31 drivers/fsi/occ.c | 625 +++++++++++++++++++++++++++++++++++++++++++++++++++
32 3 files changed, 635 insertions(+)
33 create mode 100644 drivers/fsi/occ.c
34
35diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
36index 39527fa..f3d8593 100644
37--- a/drivers/fsi/Kconfig
38+++ b/drivers/fsi/Kconfig
39@@ -36,6 +36,15 @@ config FSI_SBEFIFO
40 ---help---
41 This option enables an FSI based SBEFIFO device driver.
42
43+if FSI_SBEFIFO
44+
45+config OCCFIFO
46+ tristate "OCC SBEFIFO client device driver"
47+ ---help---
48+ This option enables an SBEFIFO based OCC device driver.
49+
50+endif
51+
52 endif
53
54 endmenu
55diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
56index 851182e..336d9d5 100644
57--- a/drivers/fsi/Makefile
58+++ b/drivers/fsi/Makefile
59@@ -4,3 +4,4 @@ obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o
60 obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
61 obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
62 obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o
63+obj-$(CONFIG_OCCFIFO) += occ.o
64diff --git a/drivers/fsi/occ.c b/drivers/fsi/occ.c
65new file mode 100644
66index 0000000..74272c8
67--- /dev/null
68+++ b/drivers/fsi/occ.c
69@@ -0,0 +1,625 @@
70+/*
71+ * Copyright 2017 IBM Corp.
72+ *
73+ * This program is free software; you can redistribute it and/or modify
74+ * it under the terms of the GNU General Public License as published by
75+ * the Free Software Foundation; either version 2 of the License, or
76+ * (at your option) any later version.
77+ */
78+
79+#include <asm/unaligned.h>
80+#include <linux/device.h>
81+#include <linux/err.h>
82+#include <linux/fsi-sbefifo.h>
83+#include <linux/init.h>
84+#include <linux/kernel.h>
85+#include <linux/miscdevice.h>
86+#include <linux/module.h>
87+#include <linux/of.h>
88+#include <linux/platform_device.h>
89+#include <linux/slab.h>
90+#include <linux/uaccess.h>
91+#include <linux/wait.h>
92+#include <linux/workqueue.h>
93+
94+#define OCC_SRAM_BYTES 4096
95+#define OCC_CMD_DATA_BYTES 4090
96+#define OCC_RESP_DATA_BYTES 4089
97+
98+struct occ {
99+ struct device *sbefifo;
100+ char name[32];
101+ int idx;
102+ struct miscdevice mdev;
103+ struct list_head xfrs;
104+ spinlock_t list_lock;
105+ spinlock_t occ_lock;
106+ struct work_struct work;
107+};
108+
109+#define to_occ(x) container_of((x), struct occ, mdev)
110+
111+struct occ_command {
112+ u8 seq_no;
113+ u8 cmd_type;
114+ u16 data_length;
115+ u8 data[OCC_CMD_DATA_BYTES];
116+ u16 checksum;
117+};
118+
119+struct occ_response {
120+ u8 seq_no;
121+ u8 cmd_type;
122+ u8 return_status;
123+ u16 data_length;
124+ u8 data[OCC_RESP_DATA_BYTES];
125+ u16 checksum;
126+};
127+
128+struct occ_xfr;
129+
130+enum {
131+ CLIENT_NONBLOCKING,
132+};
133+
134+struct occ_client {
135+ struct occ *occ;
136+ struct occ_xfr *xfr;
137+ spinlock_t lock;
138+ wait_queue_head_t wait;
139+ size_t read_offset;
140+ unsigned long flags;
141+};
142+
143+enum {
144+ XFR_IN_PROGRESS,
145+ XFR_COMPLETE,
146+ XFR_CANCELED,
147+ XFR_WAITING,
148+};
149+
150+struct occ_xfr {
151+ struct list_head link;
152+ struct occ_client *client;
153+ int rc;
154+ u8 buf[OCC_SRAM_BYTES];
155+ size_t cmd_data_length;
156+ size_t resp_data_length;
157+ unsigned long flags;
158+};
159+
160+static struct workqueue_struct *occ_wq;
161+
162+static DEFINE_IDA(occ_ida);
163+
164+static void occ_enqueue_xfr(struct occ_xfr *xfr)
165+{
166+ int empty;
167+ struct occ *occ = xfr->client->occ;
168+
169+ spin_lock_irq(&occ->list_lock);
170+ empty = list_empty(&occ->xfrs);
171+ list_add_tail(&xfr->link, &occ->xfrs);
172+ spin_unlock(&occ->list_lock);
173+
174+ if (empty)
175+ queue_work(occ_wq, &occ->work);
176+}
177+
178+static int occ_open(struct inode *inode, struct file *file)
179+{
180+ struct occ_client *client;
181+ struct miscdevice *mdev = file->private_data;
182+ struct occ *occ = to_occ(mdev);
183+
184+ client = kzalloc(sizeof(*client), GFP_KERNEL);
185+ if (!client)
186+ return -ENOMEM;
187+
188+ client->occ = occ;
189+ spin_lock_init(&client->lock);
190+ init_waitqueue_head(&client->wait);
191+
192+ if (file->f_flags & O_NONBLOCK)
193+ set_bit(CLIENT_NONBLOCKING, &client->flags);
194+
195+ file->private_data = client;
196+
197+ return 0;
198+}
199+
200+static ssize_t occ_read(struct file *file, char __user *buf, size_t len,
201+ loff_t *offset)
202+{
203+ int rc;
204+ size_t bytes;
205+ struct occ_xfr *xfr;
206+ struct occ_client *client = file->private_data;
207+
208+ if (!access_ok(VERIFY_WRITE, buf, len))
209+ return -EFAULT;
210+
211+ if (len > OCC_SRAM_BYTES)
212+ return -EINVAL;
213+
214+ spin_lock_irq(&client->lock);
215+ if (!client->xfr) {
216+ /* we just finished reading all data, return 0 */
217+ if (client->read_offset) {
218+ rc = 0;
219+ client->read_offset = 0;
220+ } else
221+ rc = -ENOMSG;
222+
223+ goto done;
224+ }
225+
226+ xfr = client->xfr;
227+
228+ if (!test_bit(XFR_COMPLETE, &xfr->flags)) {
229+ if (client->flags & CLIENT_NONBLOCKING) {
230+ rc = -ERESTARTSYS;
231+ goto done;
232+ }
233+
234+ set_bit(XFR_WAITING, &xfr->flags);
235+ spin_unlock(&client->lock);
236+
237+ rc = wait_event_interruptible(client->wait,
238+ test_bit(XFR_COMPLETE, &xfr->flags) ||
239+ test_bit(XFR_CANCELED, &xfr->flags));
240+
241+ spin_lock_irq(&client->lock);
242+ if (test_bit(XFR_CANCELED, &xfr->flags)) {
243+ kfree(xfr);
244+ spin_unlock(&client->lock);
245+ kfree(client);
246+ return -EBADFD;
247+ }
248+
249+ clear_bit(XFR_WAITING, &xfr->flags);
250+ if (!test_bit(XFR_COMPLETE, &xfr->flags)) {
251+ rc = -EINTR;
252+ goto done;
253+ }
254+ }
255+
256+ if (xfr->rc) {
257+ rc = xfr->rc;
258+ goto done;
259+ }
260+
261+ bytes = min(len, xfr->resp_data_length - client->read_offset);
262+ if (copy_to_user(buf, &xfr->buf[client->read_offset], bytes)) {
263+ rc = -EFAULT;
264+ goto done;
265+ }
266+
267+ client->read_offset += bytes;
268+
269+ /* xfr done */
270+ if (client->read_offset == xfr->resp_data_length) {
271+ kfree(xfr);
272+ client->xfr = NULL;
273+ }
274+
275+ rc = bytes;
276+
277+done:
278+ spin_unlock(&client->lock);
279+ return rc;
280+}
281+
282+static ssize_t occ_write(struct file *file, const char __user *buf,
283+ size_t len, loff_t *offset)
284+{
285+ int rc;
286+ struct occ_xfr *xfr;
287+ struct occ_client *client = file->private_data;
288+
289+ if (!access_ok(VERIFY_READ, buf, len))
290+ return -EFAULT;
291+
292+ if (len > OCC_SRAM_BYTES)
293+ return -EINVAL;
294+
295+ spin_lock_irq(&client->lock);
296+ if (client->xfr) {
297+ rc = -EDEADLK;
298+ goto done;
299+ }
300+
301+ xfr = kzalloc(sizeof(*xfr), GFP_KERNEL);
302+ if (!xfr) {
303+ rc = -ENOMEM;
304+ goto done;
305+ }
306+
307+ if (copy_from_user(xfr->buf, buf, len)) {
308+ kfree(xfr);
309+ rc = -EFAULT;
310+ goto done;
311+ }
312+
313+ xfr->client = client;
314+ xfr->cmd_data_length = len;
315+ client->xfr = xfr;
316+ client->read_offset = 0;
317+
318+ occ_enqueue_xfr(xfr);
319+
320+ rc = len;
321+
322+done:
323+ spin_unlock(&client->lock);
324+ return rc;
325+}
326+
327+static int occ_release(struct inode *inode, struct file *file)
328+{
329+ struct occ_xfr *xfr;
330+ struct occ_client *client = file->private_data;
331+ struct occ *occ = client->occ;
332+
333+ spin_lock_irq(&client->lock);
334+ xfr = client->xfr;
335+ if (!xfr) {
336+ spin_unlock(&client->lock);
337+ kfree(client);
338+ return 0;
339+ }
340+
341+ spin_lock_irq(&occ->list_lock);
342+ set_bit(XFR_CANCELED, &xfr->flags);
343+ if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) {
344+ /* already deleted from list if complete */
345+ if (!test_bit(XFR_COMPLETE, &xfr->flags))
346+ list_del(&xfr->link);
347+
348+ spin_unlock(&occ->list_lock);
349+
350+ if (test_bit(XFR_WAITING, &xfr->flags)) {
351+ /* blocking read; let reader clean up */
352+ wake_up_interruptible(&client->wait);
353+ spin_unlock(&client->lock);
354+ return 0;
355+ }
356+
357+ kfree(xfr);
358+ spin_unlock(&client->lock);
359+ kfree(client);
360+ return 0;
361+ }
362+
363+ /* operation is in progress; let worker clean up*/
364+ spin_unlock(&occ->list_lock);
365+ spin_unlock(&client->lock);
366+ return 0;
367+}
368+
369+static const struct file_operations occ_fops = {
370+ .owner = THIS_MODULE,
371+ .open = occ_open,
372+ .read = occ_read,
373+ .write = occ_write,
374+ .release = occ_release,
375+};
376+
377+static int occ_getscom(struct device *sbefifo, u32 address, u8 *data)
378+{
379+ int rc;
380+ u32 buf[4];
381+ struct sbefifo_client *client;
382+ const size_t len = sizeof(buf);
383+
384+ buf[0] = cpu_to_be32(0x4);
385+ buf[1] = cpu_to_be32(0xa201);
386+ buf[2] = 0;
387+ buf[3] = cpu_to_be32(address);
388+
389+ client = sbefifo_drv_open(sbefifo, 0);
390+ if (!client)
391+ return -ENODEV;
392+
393+ rc = sbefifo_drv_write(client, (const char *)buf, len);
394+ if (rc < 0)
395+ goto done;
396+ else if (rc != len) {
397+ rc = -EMSGSIZE;
398+ goto done;
399+ }
400+
401+ rc = sbefifo_drv_read(client, (char *)buf, len);
402+ if (rc < 0)
403+ goto done;
404+ else if (rc != len) {
405+ rc = -EMSGSIZE;
406+ goto done;
407+ }
408+
409+ /* check for good response */
410+ if ((be32_to_cpu(buf[2]) >> 16) != 0xC0DE) {
411+ rc = -EFAULT;
412+ goto done;
413+ }
414+
415+ rc = 0;
416+
417+ memcpy(data, buf, sizeof(u64));
418+
419+done:
420+ sbefifo_drv_release(client);
421+ return rc;
422+}
423+
424+static int occ_putscom(struct device *sbefifo, u32 address, u8 *data)
425+{
426+ int rc;
427+ u32 buf[6];
428+ struct sbefifo_client *client;
429+ const size_t len = sizeof(buf);
430+
431+ buf[0] = cpu_to_be32(0x6);
432+ buf[1] = cpu_to_be32(0xa202);
433+ buf[2] = 0;
434+ buf[3] = cpu_to_be32(address);
435+ memcpy(&buf[4], data, sizeof(u64));
436+
437+ client = sbefifo_drv_open(sbefifo, 0);
438+ if (!client)
439+ return -ENODEV;
440+
441+ rc = sbefifo_drv_write(client, (const char *)buf, len);
442+ if (rc < 0)
443+ goto done;
444+ else if (rc != len) {
445+ rc = -EMSGSIZE;
446+ goto done;
447+ }
448+
449+ rc = 0;
450+
451+ /* ignore response */
452+ sbefifo_drv_read(client, (char *)buf, len);
453+
454+done:
455+ sbefifo_drv_release(client);
456+ return rc;
457+}
458+
459+static int occ_putscom_u32(struct device *sbefifo, u32 address, u32 data0,
460+ u32 data1)
461+{
462+ u8 buf[8];
463+ u32 raw_data0 = cpu_to_be32(data0), raw_data1 = cpu_to_be32(data1);
464+
465+ memcpy(buf, &raw_data0, 4);
466+ memcpy(buf + 4, &raw_data1, 4);
467+
468+ return occ_putscom(sbefifo, address, buf);
469+}
470+
471+static void occ_worker(struct work_struct *work)
472+{
473+ int i, empty, canceled, waiting, rc;
474+ u16 resp_data_length;
475+ struct occ *occ = container_of(work, struct occ, work);
476+ struct device *sbefifo = occ->sbefifo;
477+ struct occ_client *client;
478+ struct occ_xfr *xfr;
479+ struct occ_response *resp;
480+
481+again:
482+ spin_lock_irq(&occ->list_lock);
483+ xfr = list_first_entry(&occ->xfrs, struct occ_xfr, link);
484+ if (!xfr) {
485+ spin_unlock(&occ->list_lock);
486+ return;
487+ }
488+
489+ set_bit(XFR_IN_PROGRESS, &xfr->flags);
490+ spin_unlock(&occ->list_lock);
491+
492+ resp = (struct occ_response *)xfr->buf;
493+
494+ spin_lock_irq(&occ->occ_lock);
495+
496+ /* set address reg to occ sram command buffer */
497+ rc = occ_putscom_u32(sbefifo, 0x6D050, 0xFFFBE000, 0);
498+ if (rc)
499+ goto done;
500+
501+ /* write cmd data */
502+ for (i = 0; i < xfr->cmd_data_length; i += 8) {
503+ rc = occ_putscom(sbefifo, 0x6D055, &xfr->buf[i]);
504+ if (rc)
505+ goto done;
506+ }
507+
508+ /* trigger attention */
509+ rc = occ_putscom_u32(sbefifo, 0x6D035, 0x20010000, 0);
510+ if (rc)
511+ goto done;
512+
513+ /* set address reg to occ sram response buffer */
514+ rc = occ_putscom_u32(sbefifo, 0x6D050, 0xFFFBF000, 0);
515+ if (rc)
516+ goto done;
517+
518+ rc = occ_getscom(sbefifo, 0x6D055, xfr->buf);
519+ if (rc)
520+ goto done;
521+
522+ xfr->resp_data_length += 8;
523+
524+ resp_data_length = be16_to_cpu(get_unaligned(&resp->data_length));
525+ if (resp_data_length > OCC_RESP_DATA_BYTES) {
526+ rc = -EFAULT;
527+ goto done;
528+ }
529+
530+ /* already read 3 bytes of resp data, but also need 2 bytes chksum */
531+ for (i = 8; i < resp_data_length + 7; i += 8) {
532+ rc = occ_getscom(sbefifo, 0x6D055, &xfr->buf[i]);
533+ if (rc)
534+ goto done;
535+
536+ xfr->resp_data_length += 8;
537+ }
538+
539+ /* no errors, got all data */
540+ xfr->resp_data_length = resp_data_length + 7;
541+
542+done:
543+ spin_unlock(&occ->occ_lock);
544+
545+ xfr->rc = rc;
546+ client = xfr->client;
547+
548+ /* lock client to prevent race with read() */
549+ spin_lock_irq(&client->lock);
550+ set_bit(XFR_COMPLETE, &xfr->flags);
551+ waiting = test_bit(XFR_WAITING, &xfr->flags);
552+ spin_unlock(&client->lock);
553+
554+ spin_lock_irq(&occ->list_lock);
555+ clear_bit(XFR_IN_PROGRESS, &xfr->flags);
556+ list_del(&xfr->link);
557+ empty = list_empty(&occ->xfrs);
558+ canceled = test_bit(XFR_CANCELED, &xfr->flags);
559+ spin_unlock(&occ->list_lock);
560+
561+ if (waiting)
562+ wake_up_interruptible(&client->wait);
563+ else if (canceled) {
564+ kfree(xfr);
565+ kfree(xfr->client);
566+ }
567+
568+ if (!empty)
569+ goto again;
570+}
571+
572+static int occ_probe(struct platform_device *pdev)
573+{
574+ int rc;
575+ u32 reg;
576+ struct occ *occ;
577+ struct device *dev = &pdev->dev;
578+
579+ dev_info(dev, "Found occ device\n");
580+ occ = devm_kzalloc(dev, sizeof(*occ), GFP_KERNEL);
581+ if (!occ)
582+ return -ENOMEM;
583+
584+ occ->sbefifo = dev->parent;
585+ INIT_LIST_HEAD(&occ->xfrs);
586+ spin_lock_init(&occ->list_lock);
587+ spin_lock_init(&occ->occ_lock);
588+ INIT_WORK(&occ->work, occ_worker);
589+
590+ if (dev->of_node) {
591+ rc = of_property_read_u32(dev->of_node, "reg", &reg);
592+ if (!rc) {
593+ /* make sure we don't have a duplicate from dts */
594+ occ->idx = ida_simple_get(&occ_ida, reg, reg + 1,
595+ GFP_KERNEL);
596+ if (occ->idx < 0)
597+ occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX,
598+ GFP_KERNEL);
599+ } else
600+ occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX,
601+ GFP_KERNEL);
602+ } else
603+ occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, GFP_KERNEL);
604+
605+ snprintf(occ->name, sizeof(occ->name), "occ%d", occ->idx);
606+ occ->mdev.fops = &occ_fops;
607+ occ->mdev.minor = MISC_DYNAMIC_MINOR;
608+ occ->mdev.name = occ->name;
609+ occ->mdev.parent = dev;
610+
611+ rc = misc_register(&occ->mdev);
612+ if (rc) {
613+ dev_err(dev, "failed to register miscdevice\n");
614+ return rc;
615+ }
616+
617+ platform_set_drvdata(pdev, occ);
618+
619+ return 0;
620+}
621+
622+static int occ_remove(struct platform_device *pdev)
623+{
624+ struct occ_xfr *xfr, *tmp;
625+ struct occ *occ = platform_get_drvdata(pdev);
626+ struct occ_client *client;
627+
628+ misc_deregister(&occ->mdev);
629+
630+ spin_lock_irq(&occ->list_lock);
631+ list_for_each_entry_safe(xfr, tmp, &occ->xfrs, link) {
632+ client = xfr->client;
633+ set_bit(XFR_CANCELED, &xfr->flags);
634+
635+ if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) {
636+ list_del(&xfr->link);
637+
638+ spin_lock_irq(&client->lock);
639+ if (test_bit(XFR_WAITING, &xfr->flags)) {
640+ wake_up_interruptible(&client->wait);
641+ spin_unlock(&client->lock);
642+ } else {
643+ kfree(xfr);
644+ spin_unlock(&client->lock);
645+ kfree(client);
646+ }
647+ }
648+ }
649+ spin_unlock(&occ->list_lock);
650+
651+ flush_work(&occ->work);
652+
653+ ida_simple_remove(&occ_ida, occ->idx);
654+
655+ return 0;
656+}
657+
658+static const struct of_device_id occ_match[] = {
659+ { .compatible = "ibm,p9-occ" },
660+ { },
661+};
662+
663+static struct platform_driver occ_driver = {
664+ .driver = {
665+ .name = "occ",
666+ .of_match_table = occ_match,
667+ },
668+ .probe = occ_probe,
669+ .remove = occ_remove,
670+};
671+
672+static int occ_init(void)
673+{
674+ occ_wq = create_singlethread_workqueue("occ");
675+ if (!occ_wq)
676+ return -ENOMEM;
677+
678+ return platform_driver_register(&occ_driver);
679+}
680+
681+static void occ_exit(void)
682+{
683+ destroy_workqueue(occ_wq);
684+
685+ platform_driver_unregister(&occ_driver);
686+}
687+
688+module_init(occ_init);
689+module_exit(occ_exit);
690+
691+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
692+MODULE_DESCRIPTION("BMC P9 OCC driver");
693+MODULE_LICENSE("GPL");
694+