summaryrefslogtreecommitdiff
path: root/arch/powerpc/kernel/rtas.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/powerpc/kernel/rtas.c')
-rw-r--r--arch/powerpc/kernel/rtas.c1056
1 files changed, 832 insertions, 224 deletions
diff --git a/arch/powerpc/kernel/rtas.c b/arch/powerpc/kernel/rtas.c
index deded51a7978..31175b34856a 100644
--- a/arch/powerpc/kernel/rtas.c
+++ b/arch/powerpc/kernel/rtas.c
@@ -9,10 +9,12 @@
#define pr_fmt(fmt) "rtas: " fmt
+#include <linux/bsearch.h>
#include <linux/capability.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/init.h>
+#include <linux/kconfig.h>
#include <linux/kernel.h>
#include <linux/memblock.h>
#include <linux/of.h>
@@ -26,6 +28,7 @@
#include <linux/syscalls.h>
#include <linux/types.h>
#include <linux/uaccess.h>
+#include <linux/xarray.h>
#include <asm/delay.h>
#include <asm/firmware.h>
@@ -33,43 +36,604 @@
#include <asm/machdep.h>
#include <asm/mmu.h>
#include <asm/page.h>
+#include <asm/rtas-work-area.h>
#include <asm/rtas.h>
#include <asm/time.h>
+#include <asm/trace.h>
#include <asm/udbg.h>
+struct rtas_filter {
+ /* Indexes into the args buffer, -1 if not used */
+ const int buf_idx1;
+ const int size_idx1;
+ const int buf_idx2;
+ const int size_idx2;
+ /*
+ * Assumed buffer size per the spec if the function does not
+ * have a size parameter, e.g. ibm,errinjct. 0 if unused.
+ */
+ const int fixed_size;
+};
+
+/**
+ * struct rtas_function - Descriptor for RTAS functions.
+ *
+ * @token: Value of @name if it exists under the /rtas node.
+ * @name: Function name.
+ * @filter: If non-NULL, invoking this function via the rtas syscall is
+ * generally allowed, and @filter describes constraints on the
+ * arguments. See also @banned_for_syscall_on_le.
+ * @banned_for_syscall_on_le: Set when call via sys_rtas is generally allowed
+ * but specifically restricted on ppc64le. Such
+ * functions are believed to have no users on
+ * ppc64le, and we want to keep it that way. It does
+ * not make sense for this to be set when @filter
+ * is false.
+ */
+struct rtas_function {
+ s32 token;
+ const bool banned_for_syscall_on_le:1;
+ const char * const name;
+ const struct rtas_filter *filter;
+};
+
+static struct rtas_function rtas_function_table[] __ro_after_init = {
+ [RTAS_FNIDX__CHECK_EXCEPTION] = {
+ .name = "check-exception",
+ },
+ [RTAS_FNIDX__DISPLAY_CHARACTER] = {
+ .name = "display-character",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__EVENT_SCAN] = {
+ .name = "event-scan",
+ },
+ [RTAS_FNIDX__FREEZE_TIME_BASE] = {
+ .name = "freeze-time-base",
+ },
+ [RTAS_FNIDX__GET_POWER_LEVEL] = {
+ .name = "get-power-level",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__GET_SENSOR_STATE] = {
+ .name = "get-sensor-state",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__GET_TERM_CHAR] = {
+ .name = "get-term-char",
+ },
+ [RTAS_FNIDX__GET_TIME_OF_DAY] = {
+ .name = "get-time-of-day",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_ACTIVATE_FIRMWARE] = {
+ .name = "ibm,activate-firmware",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_CBE_START_PTCAL] = {
+ .name = "ibm,cbe-start-ptcal",
+ },
+ [RTAS_FNIDX__IBM_CBE_STOP_PTCAL] = {
+ .name = "ibm,cbe-stop-ptcal",
+ },
+ [RTAS_FNIDX__IBM_CHANGE_MSI] = {
+ .name = "ibm,change-msi",
+ },
+ [RTAS_FNIDX__IBM_CLOSE_ERRINJCT] = {
+ .name = "ibm,close-errinjct",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_CONFIGURE_BRIDGE] = {
+ .name = "ibm,configure-bridge",
+ },
+ [RTAS_FNIDX__IBM_CONFIGURE_CONNECTOR] = {
+ .name = "ibm,configure-connector",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 0, .size_idx1 = -1,
+ .buf_idx2 = 1, .size_idx2 = -1,
+ .fixed_size = 4096,
+ },
+ },
+ [RTAS_FNIDX__IBM_CONFIGURE_KERNEL_DUMP] = {
+ .name = "ibm,configure-kernel-dump",
+ },
+ [RTAS_FNIDX__IBM_CONFIGURE_PE] = {
+ .name = "ibm,configure-pe",
+ },
+ [RTAS_FNIDX__IBM_CREATE_PE_DMA_WINDOW] = {
+ .name = "ibm,create-pe-dma-window",
+ },
+ [RTAS_FNIDX__IBM_DISPLAY_MESSAGE] = {
+ .name = "ibm,display-message",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 0, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_ERRINJCT] = {
+ .name = "ibm,errinjct",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 2, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ .fixed_size = 1024,
+ },
+ },
+ [RTAS_FNIDX__IBM_EXTI2C] = {
+ .name = "ibm,exti2c",
+ },
+ [RTAS_FNIDX__IBM_GET_CONFIG_ADDR_INFO] = {
+ .name = "ibm,get-config-addr-info",
+ },
+ [RTAS_FNIDX__IBM_GET_CONFIG_ADDR_INFO2] = {
+ .name = "ibm,get-config-addr-info2",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_GET_DYNAMIC_SENSOR_STATE] = {
+ .name = "ibm,get-dynamic-sensor-state",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_GET_INDICES] = {
+ .name = "ibm,get-indices",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 2, .size_idx1 = 3,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_GET_RIO_TOPOLOGY] = {
+ .name = "ibm,get-rio-topology",
+ },
+ [RTAS_FNIDX__IBM_GET_SYSTEM_PARAMETER] = {
+ .name = "ibm,get-system-parameter",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 1, .size_idx1 = 2,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_GET_VPD] = {
+ .name = "ibm,get-vpd",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 0, .size_idx1 = -1,
+ .buf_idx2 = 1, .size_idx2 = 2,
+ },
+ },
+ [RTAS_FNIDX__IBM_GET_XIVE] = {
+ .name = "ibm,get-xive",
+ },
+ [RTAS_FNIDX__IBM_INT_OFF] = {
+ .name = "ibm,int-off",
+ },
+ [RTAS_FNIDX__IBM_INT_ON] = {
+ .name = "ibm,int-on",
+ },
+ [RTAS_FNIDX__IBM_IO_QUIESCE_ACK] = {
+ .name = "ibm,io-quiesce-ack",
+ },
+ [RTAS_FNIDX__IBM_LPAR_PERFTOOLS] = {
+ .name = "ibm,lpar-perftools",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 2, .size_idx1 = 3,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_MANAGE_FLASH_IMAGE] = {
+ .name = "ibm,manage-flash-image",
+ },
+ [RTAS_FNIDX__IBM_MANAGE_STORAGE_PRESERVATION] = {
+ .name = "ibm,manage-storage-preservation",
+ },
+ [RTAS_FNIDX__IBM_NMI_INTERLOCK] = {
+ .name = "ibm,nmi-interlock",
+ },
+ [RTAS_FNIDX__IBM_NMI_REGISTER] = {
+ .name = "ibm,nmi-register",
+ },
+ [RTAS_FNIDX__IBM_OPEN_ERRINJCT] = {
+ .name = "ibm,open-errinjct",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_OPEN_SRIOV_ALLOW_UNFREEZE] = {
+ .name = "ibm,open-sriov-allow-unfreeze",
+ },
+ [RTAS_FNIDX__IBM_OPEN_SRIOV_MAP_PE_NUMBER] = {
+ .name = "ibm,open-sriov-map-pe-number",
+ },
+ [RTAS_FNIDX__IBM_OS_TERM] = {
+ .name = "ibm,os-term",
+ },
+ [RTAS_FNIDX__IBM_PARTNER_CONTROL] = {
+ .name = "ibm,partner-control",
+ },
+ [RTAS_FNIDX__IBM_PHYSICAL_ATTESTATION] = {
+ .name = "ibm,physical-attestation",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 0, .size_idx1 = 1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_PLATFORM_DUMP] = {
+ .name = "ibm,platform-dump",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 4, .size_idx1 = 5,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_POWER_OFF_UPS] = {
+ .name = "ibm,power-off-ups",
+ },
+ [RTAS_FNIDX__IBM_QUERY_INTERRUPT_SOURCE_NUMBER] = {
+ .name = "ibm,query-interrupt-source-number",
+ },
+ [RTAS_FNIDX__IBM_QUERY_PE_DMA_WINDOW] = {
+ .name = "ibm,query-pe-dma-window",
+ },
+ [RTAS_FNIDX__IBM_READ_PCI_CONFIG] = {
+ .name = "ibm,read-pci-config",
+ },
+ [RTAS_FNIDX__IBM_READ_SLOT_RESET_STATE] = {
+ .name = "ibm,read-slot-reset-state",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_READ_SLOT_RESET_STATE2] = {
+ .name = "ibm,read-slot-reset-state2",
+ },
+ [RTAS_FNIDX__IBM_REMOVE_PE_DMA_WINDOW] = {
+ .name = "ibm,remove-pe-dma-window",
+ },
+ [RTAS_FNIDX__IBM_RESET_PE_DMA_WINDOWS] = {
+ .name = "ibm,reset-pe-dma-windows",
+ },
+ [RTAS_FNIDX__IBM_SCAN_LOG_DUMP] = {
+ .name = "ibm,scan-log-dump",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 0, .size_idx1 = 1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_SET_DYNAMIC_INDICATOR] = {
+ .name = "ibm,set-dynamic-indicator",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 2, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_SET_EEH_OPTION] = {
+ .name = "ibm,set-eeh-option",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_SET_SLOT_RESET] = {
+ .name = "ibm,set-slot-reset",
+ },
+ [RTAS_FNIDX__IBM_SET_SYSTEM_PARAMETER] = {
+ .name = "ibm,set-system-parameter",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_SET_XIVE] = {
+ .name = "ibm,set-xive",
+ },
+ [RTAS_FNIDX__IBM_SLOT_ERROR_DETAIL] = {
+ .name = "ibm,slot-error-detail",
+ },
+ [RTAS_FNIDX__IBM_SUSPEND_ME] = {
+ .name = "ibm,suspend-me",
+ .banned_for_syscall_on_le = true,
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__IBM_TUNE_DMA_PARMS] = {
+ .name = "ibm,tune-dma-parms",
+ },
+ [RTAS_FNIDX__IBM_UPDATE_FLASH_64_AND_REBOOT] = {
+ .name = "ibm,update-flash-64-and-reboot",
+ },
+ [RTAS_FNIDX__IBM_UPDATE_NODES] = {
+ .name = "ibm,update-nodes",
+ .banned_for_syscall_on_le = true,
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 0, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ .fixed_size = 4096,
+ },
+ },
+ [RTAS_FNIDX__IBM_UPDATE_PROPERTIES] = {
+ .name = "ibm,update-properties",
+ .banned_for_syscall_on_le = true,
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = 0, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ .fixed_size = 4096,
+ },
+ },
+ [RTAS_FNIDX__IBM_VALIDATE_FLASH_IMAGE] = {
+ .name = "ibm,validate-flash-image",
+ },
+ [RTAS_FNIDX__IBM_WRITE_PCI_CONFIG] = {
+ .name = "ibm,write-pci-config",
+ },
+ [RTAS_FNIDX__NVRAM_FETCH] = {
+ .name = "nvram-fetch",
+ },
+ [RTAS_FNIDX__NVRAM_STORE] = {
+ .name = "nvram-store",
+ },
+ [RTAS_FNIDX__POWER_OFF] = {
+ .name = "power-off",
+ },
+ [RTAS_FNIDX__PUT_TERM_CHAR] = {
+ .name = "put-term-char",
+ },
+ [RTAS_FNIDX__QUERY_CPU_STOPPED_STATE] = {
+ .name = "query-cpu-stopped-state",
+ },
+ [RTAS_FNIDX__READ_PCI_CONFIG] = {
+ .name = "read-pci-config",
+ },
+ [RTAS_FNIDX__RTAS_LAST_ERROR] = {
+ .name = "rtas-last-error",
+ },
+ [RTAS_FNIDX__SET_INDICATOR] = {
+ .name = "set-indicator",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__SET_POWER_LEVEL] = {
+ .name = "set-power-level",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__SET_TIME_FOR_POWER_ON] = {
+ .name = "set-time-for-power-on",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__SET_TIME_OF_DAY] = {
+ .name = "set-time-of-day",
+ .filter = &(const struct rtas_filter) {
+ .buf_idx1 = -1, .size_idx1 = -1,
+ .buf_idx2 = -1, .size_idx2 = -1,
+ },
+ },
+ [RTAS_FNIDX__START_CPU] = {
+ .name = "start-cpu",
+ },
+ [RTAS_FNIDX__STOP_SELF] = {
+ .name = "stop-self",
+ },
+ [RTAS_FNIDX__SYSTEM_REBOOT] = {
+ .name = "system-reboot",
+ },
+ [RTAS_FNIDX__THAW_TIME_BASE] = {
+ .name = "thaw-time-base",
+ },
+ [RTAS_FNIDX__WRITE_PCI_CONFIG] = {
+ .name = "write-pci-config",
+ },
+};
+
+/**
+ * rtas_function_token() - RTAS function token lookup.
+ * @handle: Function handle, e.g. RTAS_FN_EVENT_SCAN.
+ *
+ * Context: Any context.
+ * Return: the token value for the function if implemented by this platform,
+ * otherwise RTAS_UNKNOWN_SERVICE.
+ */
+s32 rtas_function_token(const rtas_fn_handle_t handle)
+{
+ const size_t index = handle.index;
+ const bool out_of_bounds = index >= ARRAY_SIZE(rtas_function_table);
+
+ if (WARN_ONCE(out_of_bounds, "invalid function index %zu", index))
+ return RTAS_UNKNOWN_SERVICE;
+ /*
+ * Various drivers attempt token lookups on non-RTAS
+ * platforms.
+ */
+ if (!rtas.dev)
+ return RTAS_UNKNOWN_SERVICE;
+
+ return rtas_function_table[index].token;
+}
+EXPORT_SYMBOL_GPL(rtas_function_token);
+
+static int rtas_function_cmp(const void *a, const void *b)
+{
+ const struct rtas_function *f1 = a;
+ const struct rtas_function *f2 = b;
+
+ return strcmp(f1->name, f2->name);
+}
+
+/*
+ * Boot-time initialization of the function table needs the lookup to
+ * return a non-const-qualified object. Use rtas_name_to_function()
+ * in all other contexts.
+ */
+static struct rtas_function *__rtas_name_to_function(const char *name)
+{
+ const struct rtas_function key = {
+ .name = name,
+ };
+ struct rtas_function *found;
+
+ found = bsearch(&key, rtas_function_table, ARRAY_SIZE(rtas_function_table),
+ sizeof(rtas_function_table[0]), rtas_function_cmp);
+
+ return found;
+}
+
+static const struct rtas_function *rtas_name_to_function(const char *name)
+{
+ return __rtas_name_to_function(name);
+}
+
+static DEFINE_XARRAY(rtas_token_to_function_xarray);
+
+static int __init rtas_token_to_function_xarray_init(void)
+{
+ int err = 0;
+
+ for (size_t i = 0; i < ARRAY_SIZE(rtas_function_table); ++i) {
+ const struct rtas_function *func = &rtas_function_table[i];
+ const s32 token = func->token;
+
+ if (token == RTAS_UNKNOWN_SERVICE)
+ continue;
+
+ err = xa_err(xa_store(&rtas_token_to_function_xarray,
+ token, (void *)func, GFP_KERNEL));
+ if (err)
+ break;
+ }
+
+ return err;
+}
+arch_initcall(rtas_token_to_function_xarray_init);
+
+static const struct rtas_function *rtas_token_to_function(s32 token)
+{
+ const struct rtas_function *func;
+
+ if (WARN_ONCE(token < 0, "invalid token %d", token))
+ return NULL;
+
+ func = xa_load(&rtas_token_to_function_xarray, token);
+
+ if (WARN_ONCE(!func, "unexpected failed lookup for token %d", token))
+ return NULL;
+
+ return func;
+}
+
/* This is here deliberately so it's only used in this file */
void enter_rtas(unsigned long);
-static inline void do_enter_rtas(unsigned long args)
+static void __do_enter_rtas(struct rtas_args *args)
+{
+ enter_rtas(__pa(args));
+ srr_regs_clobbered(); /* rtas uses SRRs, invalidate */
+}
+
+static void __do_enter_rtas_trace(struct rtas_args *args)
{
- unsigned long msr;
+ const char *name = NULL;
+ /*
+ * If the tracepoints that consume the function name aren't
+ * active, avoid the lookup.
+ */
+ if ((trace_rtas_input_enabled() || trace_rtas_output_enabled())) {
+ const s32 token = be32_to_cpu(args->token);
+ const struct rtas_function *func = rtas_token_to_function(token);
+
+ name = func->name;
+ }
+
+ trace_rtas_input(args, name);
+ trace_rtas_ll_entry(args);
+
+ __do_enter_rtas(args);
+
+ trace_rtas_ll_exit(args);
+ trace_rtas_output(args, name);
+}
+static void do_enter_rtas(struct rtas_args *args)
+{
+ const unsigned long msr = mfmsr();
+ /*
+ * Situations where we want to skip any active tracepoints for
+ * safety reasons:
+ *
+ * 1. The last code executed on an offline CPU as it stops,
+ * i.e. we're about to call stop-self. The tracepoints'
+ * function name lookup uses xarray, which uses RCU, which
+ * isn't valid to call on an offline CPU. Any events
+ * emitted on an offline CPU will be discarded anyway.
+ *
+ * 2. In real mode, as when invoking ibm,nmi-interlock from
+ * the pseries MCE handler. We cannot count on trace
+ * buffers or the entries in rtas_token_to_function_xarray
+ * to be contained in the RMO.
+ */
+ const unsigned long mask = MSR_IR | MSR_DR;
+ const bool can_trace = likely(cpu_online(raw_smp_processor_id()) &&
+ (msr & mask) == mask);
/*
* Make sure MSR[RI] is currently enabled as it will be forced later
* in enter_rtas.
*/
- msr = mfmsr();
BUG_ON(!(msr & MSR_RI));
BUG_ON(!irqs_disabled());
hard_irq_disable(); /* Ensure MSR[EE] is disabled on PPC64 */
- enter_rtas(args);
-
- srr_regs_clobbered(); /* rtas uses SRRs, invalidate */
+ if (can_trace)
+ __do_enter_rtas_trace(args);
+ else
+ __do_enter_rtas(args);
}
-struct rtas_t rtas = {
- .lock = __ARCH_SPIN_LOCK_UNLOCKED
-};
-EXPORT_SYMBOL(rtas);
+struct rtas_t rtas;
+
+/*
+ * Nearly all RTAS calls need to be serialized. All uses of the
+ * default rtas_args block must hold rtas_lock.
+ *
+ * Exceptions to the RTAS serialization requirement (e.g. stop-self)
+ * must use a separate rtas_args structure.
+ */
+static DEFINE_RAW_SPINLOCK(rtas_lock);
+static struct rtas_args rtas_args;
DEFINE_SPINLOCK(rtas_data_buf_lock);
-EXPORT_SYMBOL(rtas_data_buf_lock);
+EXPORT_SYMBOL_GPL(rtas_data_buf_lock);
-char rtas_data_buf[RTAS_DATA_BUF_SIZE] __cacheline_aligned;
-EXPORT_SYMBOL(rtas_data_buf);
+char rtas_data_buf[RTAS_DATA_BUF_SIZE] __aligned(SZ_4K);
+EXPORT_SYMBOL_GPL(rtas_data_buf);
unsigned long rtas_rmo_buf;
@@ -78,29 +642,7 @@ unsigned long rtas_rmo_buf;
* This is done like this so rtas_flash can be a module.
*/
void (*rtas_flash_term_hook)(int);
-EXPORT_SYMBOL(rtas_flash_term_hook);
-
-/* RTAS use home made raw locking instead of spin_lock_irqsave
- * because those can be called from within really nasty contexts
- * such as having the timebase stopped which would lockup with
- * normal locks and spinlock debugging enabled
- */
-static unsigned long lock_rtas(void)
-{
- unsigned long flags;
-
- local_irq_save(flags);
- preempt_disable();
- arch_spin_lock(&rtas.lock);
- return flags;
-}
-
-static void unlock_rtas(unsigned long flags)
-{
- arch_spin_unlock(&rtas.lock);
- local_irq_restore(flags);
- preempt_enable();
-}
+EXPORT_SYMBOL_GPL(rtas_flash_term_hook);
/*
* call_rtas_display_status and call_rtas_display_status_delay
@@ -109,14 +651,14 @@ static void unlock_rtas(unsigned long flags)
*/
static void call_rtas_display_status(unsigned char c)
{
- unsigned long s;
+ unsigned long flags;
if (!rtas.base)
return;
- s = lock_rtas();
- rtas_call_unlocked(&rtas.args, 10, 1, 1, NULL, c);
- unlock_rtas(s);
+ raw_spin_lock_irqsave(&rtas_lock, flags);
+ rtas_call_unlocked(&rtas_args, 10, 1, 1, NULL, c);
+ raw_spin_unlock_irqrestore(&rtas_lock, flags);
}
static void call_rtas_display_status_delay(char c)
@@ -240,8 +782,8 @@ void rtas_progress(char *s, unsigned short hex)
"ibm,display-truncation-length", NULL);
of_node_put(root);
}
- display_character = rtas_token("display-character");
- set_indicator = rtas_token("set-indicator");
+ display_character = rtas_function_token(RTAS_FN_DISPLAY_CHARACTER);
+ set_indicator = rtas_function_token(RTAS_FN_SET_INDICATOR);
}
if (display_character == RTAS_UNKNOWN_SERVICE) {
@@ -326,23 +868,38 @@ void rtas_progress(char *s, unsigned short hex)
spin_unlock(&progress_lock);
}
-EXPORT_SYMBOL(rtas_progress); /* needed by rtas_flash module */
+EXPORT_SYMBOL_GPL(rtas_progress); /* needed by rtas_flash module */
int rtas_token(const char *service)
{
+ const struct rtas_function *func;
const __be32 *tokp;
+
if (rtas.dev == NULL)
return RTAS_UNKNOWN_SERVICE;
+
+ func = rtas_name_to_function(service);
+ if (func)
+ return func->token;
+ /*
+ * The caller is looking up a name that is not known to be an
+ * RTAS function. Either it's a function that needs to be
+ * added to the table, or they're misusing rtas_token() to
+ * access non-function properties of the /rtas node. Warn and
+ * fall back to the legacy behavior.
+ */
+ WARN_ONCE(1, "unknown function `%s`, should it be added to rtas_function_table?\n",
+ service);
+
tokp = of_get_property(rtas.dev, service, NULL);
return tokp ? be32_to_cpu(*tokp) : RTAS_UNKNOWN_SERVICE;
}
-EXPORT_SYMBOL(rtas_token);
+EXPORT_SYMBOL_GPL(rtas_token);
int rtas_service_present(const char *service)
{
return rtas_token(service) != RTAS_UNKNOWN_SERVICE;
}
-EXPORT_SYMBOL(rtas_service_present);
#ifdef CONFIG_RTAS_ERROR_LOGGING
@@ -357,7 +914,6 @@ int rtas_get_error_log_max(void)
{
return rtas_error_log_max;
}
-EXPORT_SYMBOL(rtas_get_error_log_max);
static void __init init_error_log_max(void)
{
@@ -381,39 +937,39 @@ static void __init init_error_log_max(void)
static char rtas_err_buf[RTAS_ERROR_LOG_MAX];
-static int rtas_last_error_token;
/** Return a copy of the detailed error text associated with the
* most recent failed call to rtas. Because the error text
* might go stale if there are any other intervening rtas calls,
* this routine must be called atomically with whatever produced
- * the error (i.e. with rtas.lock still held from the previous call).
+ * the error (i.e. with rtas_lock still held from the previous call).
*/
static char *__fetch_rtas_last_error(char *altbuf)
{
+ const s32 token = rtas_function_token(RTAS_FN_RTAS_LAST_ERROR);
struct rtas_args err_args, save_args;
u32 bufsz;
char *buf = NULL;
- if (rtas_last_error_token == -1)
+ if (token == -1)
return NULL;
bufsz = rtas_get_error_log_max();
- err_args.token = cpu_to_be32(rtas_last_error_token);
+ err_args.token = cpu_to_be32(token);
err_args.nargs = cpu_to_be32(2);
err_args.nret = cpu_to_be32(1);
err_args.args[0] = cpu_to_be32(__pa(rtas_err_buf));
err_args.args[1] = cpu_to_be32(bufsz);
err_args.args[2] = 0;
- save_args = rtas.args;
- rtas.args = err_args;
+ save_args = rtas_args;
+ rtas_args = err_args;
- do_enter_rtas(__pa(&rtas.args));
+ do_enter_rtas(&rtas_args);
- err_args = rtas.args;
- rtas.args = save_args;
+ err_args = rtas_args;
+ rtas_args = save_args;
/* Log the error in the unlikely case that there was one. */
if (unlikely(err_args.args[2] == 0)) {
@@ -457,7 +1013,7 @@ va_rtas_call_unlocked(struct rtas_args *args, int token, int nargs, int nret,
for (i = 0; i < nret; ++i)
args->rets[i] = 0;
- do_enter_rtas(__pa(args));
+ do_enter_rtas(args);
}
void rtas_call_unlocked(struct rtas_args *args, int token, int nargs, int nret, ...)
@@ -469,8 +1025,11 @@ void rtas_call_unlocked(struct rtas_args *args, int token, int nargs, int nret,
va_end(list);
}
-static int ibm_open_errinjct_token;
-static int ibm_errinjct_token;
+static bool token_is_restricted_errinjct(s32 token)
+{
+ return token == rtas_function_token(RTAS_FN_IBM_OPEN_ERRINJCT) ||
+ token == rtas_function_token(RTAS_FN_IBM_ERRINJCT);
+}
/**
* rtas_call() - Invoke an RTAS firmware function.
@@ -481,7 +1040,7 @@ static int ibm_errinjct_token;
* @....: List of @nargs input parameters.
*
* Invokes the RTAS function indicated by @token, which the caller
- * should obtain via rtas_token().
+ * should obtain via rtas_function_token().
*
* The @nargs and @nret arguments must match the number of input and
* output parameters specified for the RTAS function.
@@ -534,15 +1093,15 @@ int rtas_call(int token, int nargs, int nret, int *outputs, ...)
{
va_list list;
int i;
- unsigned long s;
- struct rtas_args *rtas_args;
+ unsigned long flags;
+ struct rtas_args *args;
char *buff_copy = NULL;
int ret;
if (!rtas.entry || token == RTAS_UNKNOWN_SERVICE)
return -1;
- if (token == ibm_open_errinjct_token || token == ibm_errinjct_token) {
+ if (token_is_restricted_errinjct(token)) {
/*
* It would be nicer to not discard the error value
* from security_locked_down(), but callers expect an
@@ -557,26 +1116,25 @@ int rtas_call(int token, int nargs, int nret, int *outputs, ...)
return -1;
}
- s = lock_rtas();
-
+ raw_spin_lock_irqsave(&rtas_lock, flags);
/* We use the global rtas args buffer */
- rtas_args = &rtas.args;
+ args = &rtas_args;
va_start(list, outputs);
- va_rtas_call_unlocked(rtas_args, token, nargs, nret, list);
+ va_rtas_call_unlocked(args, token, nargs, nret, list);
va_end(list);
/* A -1 return code indicates that the last command couldn't
be completed due to a hardware error. */
- if (be32_to_cpu(rtas_args->rets[0]) == -1)
+ if (be32_to_cpu(args->rets[0]) == -1)
buff_copy = __fetch_rtas_last_error(NULL);
if (nret > 1 && outputs != NULL)
for (i = 0; i < nret-1; ++i)
- outputs[i] = be32_to_cpu(rtas_args->rets[i+1]);
- ret = (nret > 0)? be32_to_cpu(rtas_args->rets[0]): 0;
+ outputs[i] = be32_to_cpu(args->rets[i + 1]);
+ ret = (nret > 0) ? be32_to_cpu(args->rets[0]) : 0;
- unlock_rtas(s);
+ raw_spin_unlock_irqrestore(&rtas_lock, flags);
if (buff_copy) {
log_error(buff_copy, ERR_TYPE_RTAS_LOG, 0);
@@ -585,7 +1143,7 @@ int rtas_call(int token, int nargs, int nret, int *outputs, ...)
}
return ret;
}
-EXPORT_SYMBOL(rtas_call);
+EXPORT_SYMBOL_GPL(rtas_call);
/**
* rtas_busy_delay_time() - From an RTAS status value, calculate the
@@ -623,7 +1181,47 @@ unsigned int rtas_busy_delay_time(int status)
return ms;
}
-EXPORT_SYMBOL(rtas_busy_delay_time);
+
+/*
+ * Early boot fallback for rtas_busy_delay().
+ */
+static bool __init rtas_busy_delay_early(int status)
+{
+ static size_t successive_ext_delays __initdata;
+ bool retry;
+
+ switch (status) {
+ case RTAS_EXTENDED_DELAY_MIN...RTAS_EXTENDED_DELAY_MAX:
+ /*
+ * In the unlikely case that we receive an extended
+ * delay status in early boot, the OS is probably not
+ * the cause, and there's nothing we can do to clear
+ * the condition. Best we can do is delay for a bit
+ * and hope it's transient. Lie to the caller if it
+ * seems like we're stuck in a retry loop.
+ */
+ mdelay(1);
+ retry = true;
+ successive_ext_delays += 1;
+ if (successive_ext_delays > 1000) {
+ pr_err("too many extended delays, giving up\n");
+ dump_stack();
+ retry = false;
+ successive_ext_delays = 0;
+ }
+ break;
+ case RTAS_BUSY:
+ retry = true;
+ successive_ext_delays = 0;
+ break;
+ default:
+ retry = false;
+ successive_ext_delays = 0;
+ break;
+ }
+
+ return retry;
+}
/**
* rtas_busy_delay() - helper for RTAS busy and extended delay statuses
@@ -643,11 +1241,17 @@ EXPORT_SYMBOL(rtas_busy_delay_time);
* * false - @status is not @RTAS_BUSY nor an extended delay hint. The
* caller is responsible for handling @status.
*/
-bool rtas_busy_delay(int status)
+bool __ref rtas_busy_delay(int status)
{
unsigned int ms;
bool ret;
+ /*
+ * Can't do timed sleeps before timekeeping is up.
+ */
+ if (system_state < SYSTEM_SCHEDULING)
+ return rtas_busy_delay_early(status);
+
switch (status) {
case RTAS_EXTENDED_DELAY_MIN...RTAS_EXTENDED_DELAY_MAX:
ret = true;
@@ -697,7 +1301,7 @@ bool rtas_busy_delay(int status)
return ret;
}
-EXPORT_SYMBOL(rtas_busy_delay);
+EXPORT_SYMBOL_GPL(rtas_busy_delay);
static int rtas_error_rc(int rtas_rc)
{
@@ -729,7 +1333,7 @@ static int rtas_error_rc(int rtas_rc)
int rtas_get_power_level(int powerdomain, int *level)
{
- int token = rtas_token("get-power-level");
+ int token = rtas_function_token(RTAS_FN_GET_POWER_LEVEL);
int rc;
if (token == RTAS_UNKNOWN_SERVICE)
@@ -742,11 +1346,11 @@ int rtas_get_power_level(int powerdomain, int *level)
return rtas_error_rc(rc);
return rc;
}
-EXPORT_SYMBOL(rtas_get_power_level);
+EXPORT_SYMBOL_GPL(rtas_get_power_level);
int rtas_set_power_level(int powerdomain, int level, int *setlevel)
{
- int token = rtas_token("set-power-level");
+ int token = rtas_function_token(RTAS_FN_SET_POWER_LEVEL);
int rc;
if (token == RTAS_UNKNOWN_SERVICE)
@@ -760,11 +1364,11 @@ int rtas_set_power_level(int powerdomain, int level, int *setlevel)
return rtas_error_rc(rc);
return rc;
}
-EXPORT_SYMBOL(rtas_set_power_level);
+EXPORT_SYMBOL_GPL(rtas_set_power_level);
int rtas_get_sensor(int sensor, int index, int *state)
{
- int token = rtas_token("get-sensor-state");
+ int token = rtas_function_token(RTAS_FN_GET_SENSOR_STATE);
int rc;
if (token == RTAS_UNKNOWN_SERVICE)
@@ -778,11 +1382,11 @@ int rtas_get_sensor(int sensor, int index, int *state)
return rtas_error_rc(rc);
return rc;
}
-EXPORT_SYMBOL(rtas_get_sensor);
+EXPORT_SYMBOL_GPL(rtas_get_sensor);
int rtas_get_sensor_fast(int sensor, int index, int *state)
{
- int token = rtas_token("get-sensor-state");
+ int token = rtas_function_token(RTAS_FN_GET_SENSOR_STATE);
int rc;
if (token == RTAS_UNKNOWN_SERVICE)
@@ -821,11 +1425,10 @@ bool rtas_indicator_present(int token, int *maxindex)
return false;
}
-EXPORT_SYMBOL(rtas_indicator_present);
int rtas_set_indicator(int indicator, int index, int new_value)
{
- int token = rtas_token("set-indicator");
+ int token = rtas_function_token(RTAS_FN_SET_INDICATOR);
int rc;
if (token == RTAS_UNKNOWN_SERVICE)
@@ -839,15 +1442,15 @@ int rtas_set_indicator(int indicator, int index, int new_value)
return rtas_error_rc(rc);
return rc;
}
-EXPORT_SYMBOL(rtas_set_indicator);
+EXPORT_SYMBOL_GPL(rtas_set_indicator);
/*
* Ignoring RTAS extended delay
*/
int rtas_set_indicator_fast(int indicator, int index, int new_value)
{
+ int token = rtas_function_token(RTAS_FN_SET_INDICATOR);
int rc;
- int token = rtas_token("set-indicator");
if (token == RTAS_UNKNOWN_SERVICE)
return -ENOENT;
@@ -889,10 +1492,11 @@ int rtas_set_indicator_fast(int indicator, int index, int new_value)
*/
int rtas_ibm_suspend_me(int *fw_status)
{
+ int token = rtas_function_token(RTAS_FN_IBM_SUSPEND_ME);
int fwrc;
int ret;
- fwrc = rtas_call(rtas_token("ibm,suspend-me"), 0, 1, NULL);
+ fwrc = rtas_call(token, 0, 1, NULL);
switch (fwrc) {
case 0:
@@ -925,7 +1529,7 @@ void __noreturn rtas_restart(char *cmd)
if (rtas_flash_term_hook)
rtas_flash_term_hook(SYS_RESTART);
pr_emerg("system-reboot returned %d\n",
- rtas_call(rtas_token("system-reboot"), 0, 1, NULL));
+ rtas_call(rtas_function_token(RTAS_FN_SYSTEM_REBOOT), 0, 1, NULL));
for (;;);
}
@@ -935,7 +1539,7 @@ void rtas_power_off(void)
rtas_flash_term_hook(SYS_POWER_OFF);
/* allow power on only with power button press */
pr_emerg("power-off returned %d\n",
- rtas_call(rtas_token("power-off"), 2, 1, NULL, -1, -1));
+ rtas_call(rtas_function_token(RTAS_FN_POWER_OFF), 2, 1, NULL, -1, -1));
for (;;);
}
@@ -945,16 +1549,17 @@ void __noreturn rtas_halt(void)
rtas_flash_term_hook(SYS_HALT);
/* allow power on only with power button press */
pr_emerg("power-off returned %d\n",
- rtas_call(rtas_token("power-off"), 2, 1, NULL, -1, -1));
+ rtas_call(rtas_function_token(RTAS_FN_POWER_OFF), 2, 1, NULL, -1, -1));
for (;;);
}
/* Must be in the RMO region, so we place it here */
static char rtas_os_term_buf[2048];
-static s32 ibm_os_term_token = RTAS_UNKNOWN_SERVICE;
+static bool ibm_extended_os_term;
void rtas_os_term(char *str)
{
+ s32 token = rtas_function_token(RTAS_FN_IBM_OS_TERM);
int status;
/*
@@ -963,7 +1568,8 @@ void rtas_os_term(char *str)
* this property may terminate the partition which we want to avoid
* since it interferes with panic_timeout.
*/
- if (ibm_os_term_token == RTAS_UNKNOWN_SERVICE)
+
+ if (token == RTAS_UNKNOWN_SERVICE || !ibm_extended_os_term)
return;
snprintf(rtas_os_term_buf, 2048, "OS panic: %s", str);
@@ -974,8 +1580,7 @@ void rtas_os_term(char *str)
* schedules.
*/
do {
- status = rtas_call(ibm_os_term_token, 1, 1, NULL,
- __pa(rtas_os_term_buf));
+ status = rtas_call(token, 1, 1, NULL, __pa(rtas_os_term_buf));
} while (rtas_busy_delay_time(status));
if (status != 0)
@@ -995,10 +1600,9 @@ void rtas_os_term(char *str)
*/
void rtas_activate_firmware(void)
{
- int token;
+ int token = rtas_function_token(RTAS_FN_IBM_ACTIVATE_FIRMWARE);
int fwrc;
- token = rtas_token("ibm,activate-firmware");
if (token == RTAS_UNKNOWN_SERVICE) {
pr_notice("ibm,activate-firmware method unavailable\n");
return;
@@ -1063,56 +1667,12 @@ noinstr struct pseries_errorlog *get_pseries_errorlog(struct rtas_error_log *log
*
* Accordingly, we filter RTAS requests to check that the call is
* permitted, and that provided pointers fall within the RMO buffer.
- * The rtas_filters list contains an entry for each permitted call,
- * with the indexes of the parameters which are expected to contain
- * addresses and sizes of buffers allocated inside the RMO buffer.
+ * If a function is allowed to be invoked via the syscall, then its
+ * entry in the rtas_functions table points to a rtas_filter that
+ * describes its constraints, with the indexes of the parameters which
+ * are expected to contain addresses and sizes of buffers allocated
+ * inside the RMO buffer.
*/
-struct rtas_filter {
- const char *name;
- int token;
- /* Indexes into the args buffer, -1 if not used */
- int buf_idx1;
- int size_idx1;
- int buf_idx2;
- int size_idx2;
-
- int fixed_size;
-};
-
-static struct rtas_filter rtas_filters[] __ro_after_init = {
- { "ibm,activate-firmware", -1, -1, -1, -1, -1 },
- { "ibm,configure-connector", -1, 0, -1, 1, -1, 4096 }, /* Special cased */
- { "display-character", -1, -1, -1, -1, -1 },
- { "ibm,display-message", -1, 0, -1, -1, -1 },
- { "ibm,errinjct", -1, 2, -1, -1, -1, 1024 },
- { "ibm,close-errinjct", -1, -1, -1, -1, -1 },
- { "ibm,open-errinjct", -1, -1, -1, -1, -1 },
- { "ibm,get-config-addr-info2", -1, -1, -1, -1, -1 },
- { "ibm,get-dynamic-sensor-state", -1, 1, -1, -1, -1 },
- { "ibm,get-indices", -1, 2, 3, -1, -1 },
- { "get-power-level", -1, -1, -1, -1, -1 },
- { "get-sensor-state", -1, -1, -1, -1, -1 },
- { "ibm,get-system-parameter", -1, 1, 2, -1, -1 },
- { "get-time-of-day", -1, -1, -1, -1, -1 },
- { "ibm,get-vpd", -1, 0, -1, 1, 2 },
- { "ibm,lpar-perftools", -1, 2, 3, -1, -1 },
- { "ibm,platform-dump", -1, 4, 5, -1, -1 }, /* Special cased */
- { "ibm,read-slot-reset-state", -1, -1, -1, -1, -1 },
- { "ibm,scan-log-dump", -1, 0, 1, -1, -1 },
- { "ibm,set-dynamic-indicator", -1, 2, -1, -1, -1 },
- { "ibm,set-eeh-option", -1, -1, -1, -1, -1 },
- { "set-indicator", -1, -1, -1, -1, -1 },
- { "set-power-level", -1, -1, -1, -1, -1 },
- { "set-time-for-power-on", -1, -1, -1, -1, -1 },
- { "ibm,set-system-parameter", -1, 1, -1, -1, -1 },
- { "set-time-of-day", -1, -1, -1, -1, -1 },
-#ifdef CONFIG_CPU_BIG_ENDIAN
- { "ibm,suspend-me", -1, -1, -1, -1, -1 },
- { "ibm,update-nodes", -1, 0, -1, -1, -1, 4096 },
- { "ibm,update-properties", -1, 0, -1, -1, -1, 4096 },
-#endif
- { "ibm,physical-attestation", -1, 0, 1, -1, -1 },
-};
static bool in_rmo_buf(u32 base, u32 end)
{
@@ -1126,63 +1686,75 @@ static bool in_rmo_buf(u32 base, u32 end)
static bool block_rtas_call(int token, int nargs,
struct rtas_args *args)
{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(rtas_filters); i++) {
- struct rtas_filter *f = &rtas_filters[i];
- u32 base, size, end;
+ const struct rtas_function *func;
+ const struct rtas_filter *f;
+ const bool is_platform_dump = token == rtas_function_token(RTAS_FN_IBM_PLATFORM_DUMP);
+ const bool is_config_conn = token == rtas_function_token(RTAS_FN_IBM_CONFIGURE_CONNECTOR);
+ u32 base, size, end;
- if (token != f->token)
- continue;
-
- if (f->buf_idx1 != -1) {
- base = be32_to_cpu(args->args[f->buf_idx1]);
- if (f->size_idx1 != -1)
- size = be32_to_cpu(args->args[f->size_idx1]);
- else if (f->fixed_size)
- size = f->fixed_size;
- else
- size = 1;
-
- end = base + size - 1;
+ /*
+ * If this token doesn't correspond to a function the kernel
+ * understands, you're not allowed to call it.
+ */
+ func = rtas_token_to_function(token);
+ if (!func)
+ goto err;
+ /*
+ * And only functions with filters attached are allowed.
+ */
+ f = func->filter;
+ if (!f)
+ goto err;
+ /*
+ * And some functions aren't allowed on LE.
+ */
+ if (IS_ENABLED(CONFIG_CPU_LITTLE_ENDIAN) && func->banned_for_syscall_on_le)
+ goto err;
+
+ if (f->buf_idx1 != -1) {
+ base = be32_to_cpu(args->args[f->buf_idx1]);
+ if (f->size_idx1 != -1)
+ size = be32_to_cpu(args->args[f->size_idx1]);
+ else if (f->fixed_size)
+ size = f->fixed_size;
+ else
+ size = 1;
- /*
- * Special case for ibm,platform-dump - NULL buffer
- * address is used to indicate end of dump processing
- */
- if (!strcmp(f->name, "ibm,platform-dump") &&
- base == 0)
- return false;
+ end = base + size - 1;
- if (!in_rmo_buf(base, end))
- goto err;
- }
+ /*
+ * Special case for ibm,platform-dump - NULL buffer
+ * address is used to indicate end of dump processing
+ */
+ if (is_platform_dump && base == 0)
+ return false;
- if (f->buf_idx2 != -1) {
- base = be32_to_cpu(args->args[f->buf_idx2]);
- if (f->size_idx2 != -1)
- size = be32_to_cpu(args->args[f->size_idx2]);
- else if (f->fixed_size)
- size = f->fixed_size;
- else
- size = 1;
- end = base + size - 1;
+ if (!in_rmo_buf(base, end))
+ goto err;
+ }
- /*
- * Special case for ibm,configure-connector where the
- * address can be 0
- */
- if (!strcmp(f->name, "ibm,configure-connector") &&
- base == 0)
- return false;
+ if (f->buf_idx2 != -1) {
+ base = be32_to_cpu(args->args[f->buf_idx2]);
+ if (f->size_idx2 != -1)
+ size = be32_to_cpu(args->args[f->size_idx2]);
+ else if (f->fixed_size)
+ size = f->fixed_size;
+ else
+ size = 1;
+ end = base + size - 1;
- if (!in_rmo_buf(base, end))
- goto err;
- }
+ /*
+ * Special case for ibm,configure-connector where the
+ * address can be 0
+ */
+ if (is_config_conn && base == 0)
+ return false;
- return false;
+ if (!in_rmo_buf(base, end))
+ goto err;
}
+ return false;
err:
pr_err_ratelimited("sys_rtas: RTAS call blocked - exploit attempt?\n");
pr_err_ratelimited("sys_rtas: token=0x%x, nargs=%d (called by %s)\n",
@@ -1190,14 +1762,6 @@ err:
return true;
}
-static void __init rtas_syscall_filter_init(void)
-{
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE(rtas_filters); i++)
- rtas_filters[i].token = rtas_token(rtas_filters[i].name);
-}
-
/* We assume to be passed big endian arguments */
SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
{
@@ -1238,7 +1802,7 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
if (block_rtas_call(token, nargs, &args))
return -EINVAL;
- if (token == ibm_open_errinjct_token || token == ibm_errinjct_token) {
+ if (token_is_restricted_errinjct(token)) {
int err;
err = security_locked_down(LOCKDOWN_RTAS_ERROR_INJECTION);
@@ -1247,7 +1811,7 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
}
/* Need to handle ibm,suspend_me call specially */
- if (token == rtas_token("ibm,suspend-me")) {
+ if (token == rtas_function_token(RTAS_FN_IBM_SUSPEND_ME)) {
/*
* rtas_ibm_suspend_me assumes the streamid handle is in cpu
@@ -1268,18 +1832,18 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
buff_copy = get_errorlog_buffer();
- flags = lock_rtas();
+ raw_spin_lock_irqsave(&rtas_lock, flags);
- rtas.args = args;
- do_enter_rtas(__pa(&rtas.args));
- args = rtas.args;
+ rtas_args = args;
+ do_enter_rtas(&rtas_args);
+ args = rtas_args;
/* A -1 return code indicates that the last command couldn't
be completed due to a hardware error. */
if (be32_to_cpu(args.rets[0]) == -1)
errbuf = __fetch_rtas_last_error(buff_copy);
- unlock_rtas(flags);
+ raw_spin_unlock_irqrestore(&rtas_lock, flags);
if (buff_copy) {
if (errbuf)
@@ -1297,6 +1861,54 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
return 0;
}
+static void __init rtas_function_table_init(void)
+{
+ struct property *prop;
+
+ for (size_t i = 0; i < ARRAY_SIZE(rtas_function_table); ++i) {
+ struct rtas_function *curr = &rtas_function_table[i];
+ struct rtas_function *prior;
+ int cmp;
+
+ curr->token = RTAS_UNKNOWN_SERVICE;
+
+ if (i == 0)
+ continue;
+ /*
+ * Ensure table is sorted correctly for binary search
+ * on function names.
+ */
+ prior = &rtas_function_table[i - 1];
+
+ cmp = strcmp(prior->name, curr->name);
+ if (cmp < 0)
+ continue;
+
+ if (cmp == 0) {
+ pr_err("'%s' has duplicate function table entries\n",
+ curr->name);
+ } else {
+ pr_err("function table unsorted: '%s' wrongly precedes '%s'\n",
+ prior->name, curr->name);
+ }
+ }
+
+ for_each_property_of_node(rtas.dev, prop) {
+ struct rtas_function *func;
+
+ if (prop->length != sizeof(u32))
+ continue;
+
+ func = __rtas_name_to_function(prop->name);
+ if (!func)
+ continue;
+
+ func->token = be32_to_cpup((__be32 *)prop->value);
+
+ pr_debug("function %s has token %u\n", func->name, func->token);
+ }
+}
+
/*
* Call early during boot, before mem init, to retrieve the RTAS
* information from the device-tree and allocate the RMO buffer for userland
@@ -1330,12 +1942,14 @@ void __init rtas_initialize(void)
init_error_log_max();
+ /* Must be called before any function token lookups */
+ rtas_function_table_init();
+
/*
- * Discover these now to avoid device tree lookups in the
+ * Discover this now to avoid a device tree lookup in the
* panic path.
*/
- if (of_property_read_bool(rtas.dev, "ibm,extended-os-term"))
- ibm_os_term_token = rtas_token("ibm,os-term");
+ ibm_extended_os_term = of_property_read_bool(rtas.dev, "ibm,extended-os-term");
/* If RTAS was found, allocate the RMO buffer for it and look for
* the stop-self token if any
@@ -1350,12 +1964,7 @@ void __init rtas_initialize(void)
panic("ERROR: RTAS: Failed to allocate %lx bytes below %pa\n",
PAGE_SIZE, &rtas_region);
-#ifdef CONFIG_RTAS_ERROR_LOGGING
- rtas_last_error_token = rtas_token("rtas-last-error");
-#endif
- ibm_open_errinjct_token = rtas_token("ibm,open-errinjct");
- ibm_errinjct_token = rtas_token("ibm,errinjct");
- rtas_syscall_filter_init();
+ rtas_work_area_reserve_arena(rtas_region);
}
int __init early_init_dt_scan_rtas(unsigned long node,
@@ -1401,23 +2010,22 @@ int __init early_init_dt_scan_rtas(unsigned long node,
return 1;
}
-static arch_spinlock_t timebase_lock;
+static DEFINE_RAW_SPINLOCK(timebase_lock);
static u64 timebase = 0;
void rtas_give_timebase(void)
{
unsigned long flags;
- local_irq_save(flags);
+ raw_spin_lock_irqsave(&timebase_lock, flags);
hard_irq_disable();
- arch_spin_lock(&timebase_lock);
- rtas_call(rtas_token("freeze-time-base"), 0, 1, NULL);
+ rtas_call(rtas_function_token(RTAS_FN_FREEZE_TIME_BASE), 0, 1, NULL);
timebase = get_tb();
- arch_spin_unlock(&timebase_lock);
+ raw_spin_unlock(&timebase_lock);
while (timebase)
barrier();
- rtas_call(rtas_token("thaw-time-base"), 0, 1, NULL);
+ rtas_call(rtas_function_token(RTAS_FN_THAW_TIME_BASE), 0, 1, NULL);
local_irq_restore(flags);
}
@@ -1425,8 +2033,8 @@ void rtas_take_timebase(void)
{
while (!timebase)
barrier();
- arch_spin_lock(&timebase_lock);
+ raw_spin_lock(&timebase_lock);
set_tb(timebase >> 32, timebase & 0xffffffff);
timebase = 0;
- arch_spin_unlock(&timebase_lock);
+ raw_spin_unlock(&timebase_lock);
}