powerpc: Add an in memory udbg console
authorAlistair Popple <alistair@popple.id.au>
Mon, 29 Apr 2013 18:07:47 +0000 (18:07 +0000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Tue, 7 May 2013 20:36:49 +0000 (06:36 +1000)
This patch adds a new udbg early debug console which utilises
statically defined input and output buffers stored within the kernel
BSS. It is primarily designed to assist with bring up of new hardware
which may not have a working console but which has a method of
reading/writing kernel memory.

This version incorporates comments made by Ben H (thanks!).

Changes from v1:
- Add memory barriers.
- Ensure updating of read/write positions is atomic.

Signed-off-by: Alistair Popple <alistair@popple.id.au>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
arch/powerpc/Kconfig.debug
arch/powerpc/include/asm/udbg.h
arch/powerpc/kernel/udbg.c
arch/powerpc/sysdev/Makefile
arch/powerpc/sysdev/udbg_memcons.c [new file with mode: 0644]

index 5416e28a753871ec02c75503e9583e8ff5abfbe9..863d877e0b5f7444f31239a2412bdec68165abea 100644 (file)
@@ -262,8 +262,31 @@ config PPC_EARLY_DEBUG_OPAL_HVSI
          Select this to enable early debugging for the PowerNV platform
          using an "hvsi" console
 
+config PPC_EARLY_DEBUG_MEMCONS
+       bool "In memory console"
+       help
+         Select this to enable early debugging using an in memory console.
+         This console provides input and output buffers stored within the
+         kernel BSS and should be safe to select on any system. A debugger
+         can then be used to read kernel output or send input to the console.
 endchoice
 
+config PPC_MEMCONS_OUTPUT_SIZE
+       int "In memory console output buffer size"
+       depends on PPC_EARLY_DEBUG_MEMCONS
+       default 4096
+       help
+         Selects the size of the output buffer (in bytes) of the in memory
+         console.
+
+config PPC_MEMCONS_INPUT_SIZE
+       int "In memory console input buffer size"
+       depends on PPC_EARLY_DEBUG_MEMCONS
+       default 128
+       help
+         Selects the size of the input buffer (in bytes) of the in memory
+         console.
+
 config PPC_EARLY_DEBUG_OPAL
        def_bool y
        depends on PPC_EARLY_DEBUG_OPAL_RAW || PPC_EARLY_DEBUG_OPAL_HVSI
index 5a7510e9d09d3865619b7a874efa7584074b5f7a..dc590919f8eb370d1568d54f7c002d58f63b2dad 100644 (file)
@@ -52,6 +52,7 @@ extern void __init udbg_init_40x_realmode(void);
 extern void __init udbg_init_cpm(void);
 extern void __init udbg_init_usbgecko(void);
 extern void __init udbg_init_wsp(void);
+extern void __init udbg_init_memcons(void);
 extern void __init udbg_init_ehv_bc(void);
 extern void __init udbg_init_ps3gelic(void);
 extern void __init udbg_init_debug_opal_raw(void);
index 13b867093499be5abccb014677371f8787390c7f..9d3fdcd66290b79c6200cd853a04eea73be1a3d0 100644 (file)
@@ -64,6 +64,9 @@ void __init udbg_early_init(void)
        udbg_init_usbgecko();
 #elif defined(CONFIG_PPC_EARLY_DEBUG_WSP)
        udbg_init_wsp();
+#elif defined(CONFIG_PPC_EARLY_DEBUG_MEMCONS)
+       /* In memory console */
+       udbg_init_memcons();
 #elif defined(CONFIG_PPC_EARLY_DEBUG_EHV_BC)
        udbg_init_ehv_bc();
 #elif defined(CONFIG_PPC_EARLY_DEBUG_PS3GELIC)
index b0a518e9759978a7255a334388d53c79a387ba61..99464a7bdb3b1b0da09493dd4601d6b02951e13e 100644 (file)
@@ -64,6 +64,8 @@ endif
 
 obj-$(CONFIG_PPC_SCOM)         += scom.o
 
+obj-$(CONFIG_PPC_EARLY_DEBUG_MEMCONS)  += udbg_memcons.o
+
 subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror
 
 obj-$(CONFIG_PPC_XICS)         += xics/
diff --git a/arch/powerpc/sysdev/udbg_memcons.c b/arch/powerpc/sysdev/udbg_memcons.c
new file mode 100644 (file)
index 0000000..ce5a7b4
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * A udbg backend which logs messages and reads input from in memory
+ * buffers.
+ *
+ * The console output can be read from memcons_output which is a
+ * circular buffer whose next write position is stored in memcons.output_pos.
+ *
+ * Input may be passed by writing into the memcons_input buffer when it is
+ * empty. The input buffer is empty when both input_pos == input_start and
+ * *input_start == '\0'.
+ *
+ * Copyright (C) 2003-2005 Anton Blanchard and Milton Miller, IBM Corp
+ * Copyright (C) 2013 Alistair Popple, IBM Corp
+ *
+ *      This program is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU General Public License
+ *      as published by the Free Software Foundation; either version
+ *      2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <asm/barrier.h>
+#include <asm/page.h>
+#include <asm/processor.h>
+#include <asm/udbg.h>
+
+struct memcons {
+       char *output_start;
+       char *output_pos;
+       char *output_end;
+       char *input_start;
+       char *input_pos;
+       char *input_end;
+};
+
+static char memcons_output[CONFIG_PPC_MEMCONS_OUTPUT_SIZE];
+static char memcons_input[CONFIG_PPC_MEMCONS_INPUT_SIZE];
+
+struct memcons memcons = {
+       .output_start = memcons_output,
+       .output_pos = memcons_output,
+       .output_end = &memcons_output[CONFIG_PPC_MEMCONS_OUTPUT_SIZE],
+       .input_start = memcons_input,
+       .input_pos = memcons_input,
+       .input_end = &memcons_input[CONFIG_PPC_MEMCONS_INPUT_SIZE],
+};
+
+void memcons_putc(char c)
+{
+       char *new_output_pos;
+
+       *memcons.output_pos = c;
+       wmb();
+       new_output_pos = memcons.output_pos + 1;
+       if (new_output_pos >= memcons.output_end)
+               new_output_pos = memcons.output_start;
+
+       memcons.output_pos = new_output_pos;
+}
+
+int memcons_getc_poll(void)
+{
+       char c;
+       char *new_input_pos;
+
+       if (*memcons.input_pos) {
+               c = *memcons.input_pos;
+
+               new_input_pos = memcons.input_pos + 1;
+               if (new_input_pos >= memcons.input_end)
+                       new_input_pos = memcons.input_start;
+               else if (*new_input_pos == '\0')
+                       new_input_pos = memcons.input_start;
+
+               *memcons.input_pos = '\0';
+               wmb();
+               memcons.input_pos = new_input_pos;
+               return c;
+       }
+
+       return -1;
+}
+
+int memcons_getc(void)
+{
+       int c;
+
+       while (1) {
+               c = memcons_getc_poll();
+               if (c == -1)
+                       cpu_relax();
+               else
+                       break;
+       }
+
+       return c;
+}
+
+void udbg_init_memcons(void)
+{
+       udbg_putc = memcons_putc;
+       udbg_getc = memcons_getc;
+       udbg_getc_poll = memcons_getc_poll;
+}