| /* |
| * powerpc RTAS |
| * |
| * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com> |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2. |
| */ |
| #include <libcflat.h> |
| #include <libfdt/libfdt.h> |
| #include <devicetree.h> |
| #include <asm/spinlock.h> |
| #include <asm/smp.h> |
| #include <asm/hcall.h> |
| #include <asm/io.h> |
| #include <asm/rtas.h> |
| |
| extern void enter_rtas(unsigned long); |
| |
| unsigned long rtas_entry; |
| static struct rtas_args rtas_args; |
| static struct spinlock rtas_lock; |
| |
| static int rtas_node(void) |
| { |
| int node = fdt_path_offset(dt_fdt(), "/rtas"); |
| |
| if (node < 0) { |
| printf("%s: /rtas: %s\n", __func__, fdt_strerror(node)); |
| abort(); |
| } |
| |
| return node; |
| } |
| |
| void rtas_init(void) |
| { |
| bool broken_sc1 = hcall_have_broken_sc1(); |
| int node = rtas_node(), len, words, i; |
| const struct fdt_property *prop; |
| u32 *data, *insns; |
| |
| if (!dt_available()) { |
| printf("%s: No device tree!\n", __func__); |
| abort(); |
| } |
| |
| prop = fdt_get_property(dt_fdt(), node, |
| "linux,rtas-entry", &len); |
| if (!prop) { |
| /* We don't have a qemu provided RTAS blob, enter_rtas |
| * will use H_RTAS directly */ |
| return; |
| } |
| data = (u32 *)prop->data; |
| rtas_entry = (unsigned long)fdt32_to_cpu(*data); |
| insns = (u32 *)rtas_entry; |
| |
| prop = fdt_get_property(dt_fdt(), node, "rtas-size", &len); |
| if (!prop) { |
| printf("%s: /rtas/rtas-size: %s\n", |
| __func__, fdt_strerror(len)); |
| abort(); |
| } |
| data = (u32 *)prop->data; |
| words = (int)fdt32_to_cpu(*data)/4; |
| |
| for (i = 0; i < words; ++i) { |
| if (broken_sc1 && insns[i] == cpu_to_be32(SC1)) |
| insns[i] = cpu_to_be32(SC1_REPLACEMENT); |
| } |
| } |
| |
| int rtas_token(const char *service, uint32_t *token) |
| { |
| const struct fdt_property *prop; |
| u32 *data; |
| |
| if (!dt_available()) |
| return RTAS_UNKNOWN_SERVICE; |
| |
| prop = fdt_get_property(dt_fdt(), rtas_node(), service, NULL); |
| if (!prop) |
| return RTAS_UNKNOWN_SERVICE; |
| |
| data = (u32 *)prop->data; |
| *token = fdt32_to_cpu(*data); |
| |
| return 0; |
| } |
| |
| static void __rtas_call(struct rtas_args *args) |
| { |
| enter_rtas(__pa(args)); |
| } |
| |
| static int rtas_call_unlocked_va(struct rtas_args *args, |
| int token, int nargs, int nret, int *outputs, |
| va_list list) |
| { |
| int ret, i; |
| |
| args->token = cpu_to_be32(token); |
| args->nargs = cpu_to_be32(nargs); |
| args->nret = cpu_to_be32(nret); |
| args->rets = &args->args[nargs]; |
| |
| for (i = 0; i < nargs; ++i) |
| args->args[i] = cpu_to_be32(va_arg(list, u32)); |
| |
| for (i = 0; i < nret; ++i) |
| args->rets[i] = 0; |
| |
| __rtas_call(args); |
| |
| if (nret > 1 && outputs != NULL) |
| for (i = 0; i < nret - 1; ++i) |
| outputs[i] = be32_to_cpu(args->rets[i + 1]); |
| |
| ret = nret > 0 ? be32_to_cpu(args->rets[0]) : 0; |
| |
| return ret; |
| } |
| |
| int rtas_call_unlocked(struct rtas_args *args, int token, int nargs, int nret, int *outputs, ...) |
| { |
| va_list list; |
| int ret; |
| |
| va_start(list, outputs); |
| ret = rtas_call_unlocked_va(args, token, nargs, nret, outputs, list); |
| va_end(list); |
| |
| return ret; |
| } |
| |
| int rtas_call(int token, int nargs, int nret, int *outputs, ...) |
| { |
| va_list list; |
| int ret; |
| |
| assert_msg(!in_usermode(), "May not make RTAS call from user mode\n"); |
| |
| spin_lock(&rtas_lock); |
| |
| va_start(list, outputs); |
| ret = rtas_call_unlocked_va(&rtas_args, token, nargs, nret, outputs, list); |
| va_end(list); |
| |
| spin_unlock(&rtas_lock); |
| |
| return ret; |
| } |
| |
| void rtas_stop_self(void) |
| { |
| struct rtas_args args; |
| uint32_t token; |
| int ret; |
| |
| ret = rtas_token("stop-self", &token); |
| if (ret) { |
| puts("RTAS stop-self not available\n"); |
| return; |
| } |
| |
| ret = rtas_call_unlocked(&args, token, 0, 1, NULL); |
| printf("RTAS stop-self returned %d\n", ret); |
| } |
| |
| void rtas_power_off(void) |
| { |
| struct rtas_args args; |
| uint32_t token; |
| int ret; |
| |
| ret = rtas_token("power-off", &token); |
| if (ret) { |
| puts("RTAS power-off not available\n"); |
| return; |
| } |
| |
| ret = rtas_call_unlocked(&args, token, 2, 1, NULL, -1, -1); |
| printf("RTAS power-off returned %d\n", ret); |
| } |