diff --git a/core/console-log.c b/core/console-log.c
index 21a1442bd..d73913894 100644
--- a/core/console-log.c
+++ b/core/console-log.c
@@ -8,6 +8,7 @@
  * Copyright 2013-2018 IBM Corp.
  */
 
+#include <opal.h>
 #include "skiboot.h"
 #include "unistd.h"
 #include "stdio.h"
@@ -15,6 +16,8 @@
 #include "timebase.h"
 #include <debug_descriptor.h>
 
+extern void os_printf(uint32_t log_level, const char *str);
+
 static int vprlog(int log_level, const char *fmt, va_list ap)
 {
 	int count;
@@ -32,6 +35,12 @@ static int vprlog(int log_level, const char *fmt, va_list ap)
 	if (log_level > (debug_descriptor.console_log_levels >> 4))
 		return 0;
 
+	if (opal_v4_os) {
+		count = vsnprintf(buffer, sizeof(buffer), fmt, ap);
+		os_printf(log_level, buffer);
+		return count;
+	}
+
 	count = snprintf(buffer, sizeof(buffer), "[%5lu.%09lu,%d] ",
 			 tb_to_secs(tb), tb_remaining_nsecs(tb), log_level);
 	count+= vsnprintf(buffer+count, sizeof(buffer)-count, fmt, ap);
diff --git a/core/console.c b/core/console.c
index 336dda7a1..9c882c6a4 100644
--- a/core/console.c
+++ b/core/console.c
@@ -259,8 +259,19 @@ ssize_t console_write(bool flush_to_drivers, const void *buf, size_t count)
 	return count;
 }
 
+extern void os_printf(uint32_t log_level, const char *str);
+
 ssize_t write(int fd __unused, const void *buf, size_t count)
 {
+	if (opal_v4_os) {
+		char *b = malloc(count+1);
+		memcpy(b, buf, count);
+		b[count] = '\0';
+		os_printf(4, b);
+		free(b);
+		return count;
+	}
+
 	return console_write(true, buf, count);
 }
 
diff --git a/core/fast-reboot.c b/core/fast-reboot.c
index e7f3b5c67..fc5dd8bf4 100644
--- a/core/fast-reboot.c
+++ b/core/fast-reboot.c
@@ -334,7 +334,12 @@ void __noreturn fast_reboot_entry(void)
 		/* Restore skiboot vectors */
 		copy_exception_vectors();
 		copy_sreset_vector();
-		patch_traps(true);
+		if (opal_v4_os) {
+			set_opal_console_to_cooked();
+			opal_v4_os = false;
+		} else {
+			patch_traps(true);
+		}
 	}
 
 	/* Must wait for others to because shared SPRs like HID0 are only set
diff --git a/core/init.c b/core/init.c
index 95c0339cf..81672b0d4 100644
--- a/core/init.c
+++ b/core/init.c
@@ -86,8 +86,6 @@ struct debug_descriptor debug_descriptor = {
 #endif
 };
 
-static void checksum_romem(void);
-
 static bool try_load_elf64_le(struct elf_hdr *header)
 {
 	struct elf64le_hdr *kh = (struct elf64le_hdr *)header;
@@ -1028,7 +1026,7 @@ static uint32_t mem_csum(void *_p, void *_e)
 
 static uint32_t romem_csum;
 
-static void checksum_romem(void)
+void checksum_romem(void)
 {
 	uint32_t csum;
 
diff --git a/core/opal.c b/core/opal.c
index 7fb4fae86..5454cac67 100644
--- a/core/opal.c
+++ b/core/opal.c
@@ -23,6 +23,7 @@
 #include <elf-abi.h>
 #include <errorlog.h>
 #include <occ.h>
+#include <mem_region.h>
 
 /* Pending events to signal via opal_poll_events */
 uint64_t opal_pending_events;
@@ -471,6 +472,53 @@ out:
 	return ret;
 }
 
+bool opal_v4_os = false;
+struct os_ops os_ops;
+
+static int64_t opal_register_os_ops(struct opal_os_ops *ops, uint64_t size)
+{
+	struct cpu_thread *cpu;
+
+	if (size < 8)
+		return OPAL_PARAMETER;
+
+	for_each_cpu(cpu) {
+		if (cpu == this_cpu())
+			continue;
+		if (cpu->state == cpu_state_os)
+			return OPAL_BUSY;
+	}
+
+	if (opal_v4_os)
+		return OPAL_WRONG_STATE;
+
+	if (!verify_romem()) {
+		prlog(PR_NOTICE, "OPAL checksums did not match\n");
+		disable_fast_reboot("Inconsistent firmware romem checksum");
+		return OPAL_WRONG_STATE;
+	}
+
+	/* v4 must handle OPAL traps */
+	patch_traps(true);
+
+	if (size >= 8) {
+		os_ops.os_printf = (void *)be64_to_cpu(ops->os_printf);
+		set_opal_console_to_raw();
+	}
+
+	checksum_romem();
+
+	opal_v4_os = true;
+
+	return OPAL_SUCCESS;
+}
+opal_call(OPAL_REGISTER_OS_OPS, opal_register_os_ops, 2);
+
+void os_printf(uint32_t log_level, const char *str)
+{
+	os_ops.os_printf(log_level, str);
+}
+
 void add_opal_node(void)
 {
 	uint64_t base, entry, size;
diff --git a/include/mem_region.h b/include/mem_region.h
index 47c3bd70c..f56eef2d8 100644
--- a/include/mem_region.h
+++ b/include/mem_region.h
@@ -73,6 +73,7 @@ struct mem_region *find_mem_region(const char *name);
 bool mem_range_is_reserved(uint64_t start, uint64_t size);
 
 /* Read-only memory checksum */
+void checksum_romem(void);
 bool verify_romem(void);
 
 #endif /* __MEMORY_REGION */
diff --git a/include/opal-api.h b/include/opal-api.h
index e5142f121..139dc1d43 100644
--- a/include/opal-api.h
+++ b/include/opal-api.h
@@ -231,7 +231,8 @@
 #define OPAL_SYM_TO_ADDR			182
 #define OPAL_REPORT_TRAP			183
 #define OPAL_FIND_VM_AREA			184
-#define OPAL_LAST				184
+#define OPAL_REGISTER_OS_OPS			185
+#define OPAL_LAST				185
 
 #define QUIESCE_HOLD			1 /* Spin all calls at entry */
 #define QUIESCE_REJECT			2 /* Fail all calls with OPAL_BUSY */
@@ -1275,6 +1276,10 @@ struct opal_vm_area {
 	__be64	vm_flags;
 };
 
+struct opal_os_ops {
+        __be64  os_printf;      /* void printf(int32_t level, const char *str) */
+};
+
 #endif /* __ASSEMBLY__ */
 
 #endif /* __OPAL_API_H */
diff --git a/include/opal-internal.h b/include/opal-internal.h
index f6ca7ac32..64f372489 100644
--- a/include/opal-internal.h
+++ b/include/opal-internal.h
@@ -18,6 +18,15 @@ struct opal_table_entry {
 	u32	nargs;
 };
 
+struct os_ops {
+        void (*os_printf)(uint32_t log_level, const char *str);
+};
+
+extern bool opal_v4_os;
+extern struct os_ops os_ops;
+
+extern void os_printf(uint32_t log_level, const char *str);
+
 #ifdef __CHECKER__
 #define __opal_func_test_arg(__func, __nargs) 0
 #else
