[RFC] [PATCH 1/3] powernv/opal: Introduce new opal_oppanel interface to expose the op_panel
Suraj Jitindar Singh
sjitindarsingh at gmail.com
Tue Aug 2 17:48:35 AEST 2016
IBM PowerNV machines with FSPs have an operator panel with a LCD display.
Currently this oppanel display can be accessed through the
powernv_op_panel kernel module. We would like to be able to access this
display easily from other places in the Kernel.
Add a new interface through which the operator panel display can be
accessed from within the kernel. The function opal_oppanel_write acts like
a print function and will respect newline and carriage return characters
while trying to print the input to the display. The function
opal_oppanel_read will provide access to what is currently in the display
buffer.
Signed-off-by: Suraj Jitindar Singh <sjitindarsingh at gmail.com>
---
MAINTAINERS | 1 +
arch/powerpc/include/asm/opal.h | 7 +
arch/powerpc/platforms/powernv/Kconfig | 4 +
arch/powerpc/platforms/powernv/Makefile | 1 +
arch/powerpc/platforms/powernv/opal-oppanel.c | 292 ++++++++++++++++++++++++++
drivers/char/Kconfig | 1 +
6 files changed, 306 insertions(+)
create mode 100644 arch/powerpc/platforms/powernv/opal-oppanel.c
diff --git a/MAINTAINERS b/MAINTAINERS
index b1703ca..95841e1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9218,6 +9218,7 @@ M: Suraj Jitindar Singh <sjitindarsingh at gmail.com>
L: linuxppc-dev at lists.ozlabs.org
S: Maintained
F: drivers/char/powernv-op-panel.c
+F: arch/powerpc/platforms/powernv/opal-oppanel.c
PNP SUPPORT
M: "Rafael J. Wysocki" <rafael.j.wysocki at intel.com>
diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
index ea9e7f4..2802e1f 100644
--- a/arch/powerpc/include/asm/opal.h
+++ b/arch/powerpc/include/asm/opal.h
@@ -286,6 +286,13 @@ static inline int opal_get_async_rc(struct opal_msg msg)
return be64_to_cpu(msg.params[1]);
}
+#if defined(CONFIG_POWERNV_OP_PANEL) || defined(CONFIG_POWERNV_OP_PANEL_MODULE)
+extern int opal_oppanel_write(char *msg);
+extern void opal_oppanel_read(char *msg);
+extern void opal_oppanel_get_size(u32 *size);
+extern int opal_oppanel_init(void);
+#endif /* CONFIG_POWERNV_OP_PANEL */
+
#endif /* __ASSEMBLY__ */
#endif /* _ASM_POWERPC_OPAL_H */
diff --git a/arch/powerpc/platforms/powernv/Kconfig b/arch/powerpc/platforms/powernv/Kconfig
index 604190c..df325f7 100644
--- a/arch/powerpc/platforms/powernv/Kconfig
+++ b/arch/powerpc/platforms/powernv/Kconfig
@@ -26,3 +26,7 @@ config OPAL_PRD
help
This enables the opal-prd driver, a facility to run processor
recovery diagnostics on OpenPower machines
+
+config POWERNV_OP_PANEL_BASE
+ bool
+ default n
diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile
index cd9711e..0565351 100644
--- a/arch/powerpc/platforms/powernv/Makefile
+++ b/arch/powerpc/platforms/powernv/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_PPC_SCOM) += opal-xscom.o
obj-$(CONFIG_MEMORY_FAILURE) += opal-memory-errors.o
obj-$(CONFIG_TRACEPOINTS) += opal-tracepoints.o
obj-$(CONFIG_OPAL_PRD) += opal-prd.o
+obj-$(CONFIG_POWERNV_OP_PANEL_BASE) += opal-oppanel.o
diff --git a/arch/powerpc/platforms/powernv/opal-oppanel.c b/arch/powerpc/platforms/powernv/opal-oppanel.c
new file mode 100644
index 0000000..1ddf414
--- /dev/null
+++ b/arch/powerpc/platforms/powernv/opal-oppanel.c
@@ -0,0 +1,292 @@
+/*
+ * OPAL PowerNV Operator Panel Display Code
+ *
+ * Copyright 2016, Suraj Jitindar Singh, IBM Corporation.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+#include <asm/opal.h>
+
+enum {
+ MODE_LF,
+ MODE_CR,
+ MODE_NC,
+};
+
+static DEFINE_MUTEX(opal_oppanel_mutex);
+
+static u32 num_lines, line_len, buf_len;
+static char *buf;
+static oppanel_line_t *lines;
+static int cursor;
+
+static inline int last_line_index(void)
+{
+ return buf_len - line_len;
+}
+
+/*
+ * oppanel_write_to_display
+ * Write the contents of buf to the physical operator panel LCD display
+ *
+ * @return: OPAL_SUCCESS on successful write
+ * else write failed
+ */
+static int oppanel_write_to_display(void)
+{
+ struct opal_msg msg;
+ int rc, token;
+
+ token = opal_async_get_token_interruptible();
+ if (token < 0) {
+ if (token != -ERESTARTSYS)
+ pr_debug("Couldn't get OPAL async token [token=%d]\n",
+ token);
+ return token;
+ }
+
+ rc = opal_write_oppanel_async(token, lines, num_lines);
+ switch (rc) {
+ case OPAL_ASYNC_COMPLETION:
+ rc = opal_async_wait_response(token, &msg);
+ if (rc) {
+ pr_debug("Failed to wait for async response [rc=%d]\n",
+ rc);
+ break;
+ }
+ rc = opal_get_async_rc(msg);
+ if (rc != OPAL_SUCCESS) {
+ pr_debug("OPAL async call returned failed [rc=%d]\n",
+ rc);
+ break;
+ }
+ case OPAL_SUCCESS:
+ break;
+ default:
+ pr_debug("OPAL write op-panel call failed [rc=%d]\n", rc);
+ }
+
+ opal_async_release_token(token);
+ return rc;
+}
+
+/*
+ * oppanel_shift_up
+ * Shift the lines in buf up by one
+ */
+static void oppanel_shift_up(void)
+{
+ int i;
+
+ /* Shift line lengths */
+ for (i = 0; i < (num_lines - 1); i++)
+ lines[i].line_len = lines[i+1].line_len;
+
+ /* i = num_lines - 1 -> last line */
+ lines[i].line_len = 0;
+ memcpy(buf, &buf[line_len], line_len * i);
+ memset(&buf[i*line_len], '\0', line_len);
+}
+
+/*
+ * oppanel_update_display
+ * Update the oppanel display based on the current buf and cursor values
+ *
+ * @param: mode, operation to perform
+ * @return: OPAL_SUCCESS on success
+ * else update failed
+ */
+static int oppanel_update_display(int mode)
+{
+ int rc;
+
+ lines[num_lines-1].line_len = cpu_to_be64(cursor + line_len - buf_len);
+ rc = oppanel_write_to_display();
+ /* May still be busy from last write call, if so we should retry */
+ while (rc != OPAL_SUCCESS) {
+ if (rc == OPAL_BUSY_EVENT || rc == -ERESTARTSYS)
+ rc = oppanel_write_to_display();
+ else
+ break;
+ }
+ switch (mode) {
+ case MODE_LF:
+ oppanel_shift_up();
+ case MODE_CR:
+ cursor = last_line_index();
+ break;
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * opal_oppanel_write
+ * Write a message (msg) to the operator panel LCD display present on IBM
+ * powernv machines
+ *
+ * @param: msg, the message buffer to be written to the display
+ * @return: 0 Success
+ * -EIO Opal call failed
+ * -EBUSY Resource currently in use
+ * -ENODEV Required resources not initialised,
+ * either init function not called yet
+ * or init failed likely due to no oppanel
+ */
+int opal_oppanel_write(char *msg)
+{
+ int rc = OPAL_SUCCESS;
+
+ if (!msg || !*msg)
+ return 0;
+ if (!buf || !buf_len || !lines)
+ return -ENODEV;
+
+ if (mutex_trylock(&opal_oppanel_mutex)) {
+ int i, prev_cursor = cursor;
+ char prev_buf[buf_len];
+
+ memcpy(prev_buf, buf, buf_len);
+
+ for (i = 0; msg[i] && rc == OPAL_SUCCESS; i++) {
+ char c = msg[i];
+
+ switch (c) {
+ case '\n':
+ rc = oppanel_update_display(MODE_LF);
+ break;
+ case '\r':
+ if (cursor != last_line_index())
+ rc = oppanel_update_display(MODE_CR);
+ break;
+ default:
+ buf[cursor++] = c;
+ /* End of display line */
+ if (cursor >= buf_len)
+ rc = oppanel_update_display(MODE_LF);
+ /* End of input buffer */
+ else if (!msg[i+1])
+ rc = oppanel_update_display(MODE_NC);
+ break;
+ }
+ }
+
+ if (rc != OPAL_SUCCESS) {
+ cursor = prev_cursor;
+ memcpy(buf, prev_buf, buf_len);
+ rc = -EIO;
+ }
+ mutex_unlock(&opal_oppanel_mutex);
+ } else
+ rc = -EBUSY;
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(opal_oppanel_write);
+
+/*
+ * opal_oppanel_read
+ * Read the operator panel message buffer and copy it to msg
+ *
+ * @param: msg, a char buffer of size atleast buf_len,
+ * as obtained by calling opal_oppanel_get_size.
+ */
+void opal_oppanel_read(char *msg)
+{
+ memcpy(msg, buf, buf_len);
+}
+EXPORT_SYMBOL_GPL(opal_oppanel_read);
+
+/*
+ * opal_oppanel_get_size
+ * Get the size of the physical operator panel display
+ *
+ * @param: size, u32 pointer to where the size value should be stored.
+ */
+void opal_oppanel_get_size(u32 *size)
+{
+ *size = buf_len;
+}
+EXPORT_SYMBOL_GPL(opal_oppanel_get_size);
+
+/*
+ * opal_oppanel_init
+ * Initalise the operator panel, this must be called before any other
+ * function in this file
+ */
+int __init opal_oppanel_init(void)
+{
+ struct platform_device *pdev;
+ struct device_node *np;
+ int rc, i;
+
+ np = of_find_node_by_path("/ibm,opal/oppanel");
+ if (!np) {
+ pr_err("Opal node 'oppanel' not found\n");
+ rc = -ENODEV;
+ goto np_put;
+ }
+
+ /* Read length and number of lines device tree properties */
+ rc = of_property_read_u32(np, "#length", &line_len);
+ if (rc) {
+ pr_err("Opal node 'oppanel', '#length' property not found\n");
+ goto np_put;
+ }
+ rc = of_property_read_u32(np, "#lines", &num_lines);
+ if (rc) {
+ pr_err("Opal node 'oppanel', '#lines' property not found\n");
+ goto np_put;
+ }
+ buf_len = line_len * num_lines;
+
+ /* Allocate Memory */
+ buf = kcalloc(buf_len, sizeof(*buf), GFP_KERNEL);
+ if (!buf) {
+ rc = -ENOMEM;
+ goto np_put;
+ }
+ lines = kcalloc(num_lines, sizeof(*lines), GFP_KERNEL);
+ if (!lines) {
+ rc = -ENOMEM;
+ goto free_buf;
+ }
+
+ /* Setup lines structure */
+ for (i = 0; i < num_lines; i++) {
+ lines[i].line_len = 0;
+ lines[i].line = cpu_to_be64(__pa(&buf[i * line_len]));
+ }
+ cursor = last_line_index();
+
+ /* Create platform device */
+ pdev = of_platform_device_create(np, NULL, NULL);
+ rc = PTR_ERR_OR_ZERO(pdev);
+ if (rc)
+ goto free_lines;
+
+ /* We still need to node_put on success */
+ rc = 0;
+ goto np_put;
+
+free_lines:
+ kfree(lines);
+free_buf:
+ kfree(buf);
+np_put:
+ of_node_put(np);
+ return rc;
+}
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 5fcb797..95f6885 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -181,6 +181,7 @@ config IBM_BSR
config POWERNV_OP_PANEL
tristate "IBM POWERNV Operator Panel Display support"
depends on PPC_POWERNV
+ select POWERNV_OP_PANEL_BASE
help
If you say Y here, a special character device node, /dev/op_panel,
will be created which exposes the operator panel display on IBM
--
2.5.5
More information about the Linuxppc-dev
mailing list