diff options
author | David Gibson <david@gibson.dropbear.id.au> | 2011-04-01 15:15:23 +1100 |
---|---|---|
committer | Alexander Graf <agraf@suse.de> | 2011-04-01 18:34:56 +0200 |
commit | 39ac8455106af1ed669b8e10223420cf1ac5b190 (patch) | |
tree | 48c935a56286ff50f76982c9d7aab1a0eb835e03 /hw | |
parent | f43e35255cffb6ac6230dd09d308f7909f823f96 (diff) |
Implement hcall based RTAS for pSeries machines
On pSeries machines, operating systems can instantiate "RTAS" (Run-Time
Abstraction Services), a runtime component of the firmware which implements
a number of low-level, infrequently used operations. On logical partitions
under a hypervisor, many of the RTAS functions require hypervisor
privilege. For simplicity, therefore, hypervisor systems typically
implement the in-partition RTAS as just a tiny wrapper around a hypercall
which actually implements the various RTAS functions.
This patch implements such a hypercall based RTAS for our emulated pSeries
machine. A tiny in-partition "firmware" calls a new hypercall, which
looks up available RTAS services in a table.
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Signed-off-by: Alexander Graf <agraf@suse.de>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/spapr.c | 26 | ||||
-rw-r--r-- | hw/spapr.h | 32 | ||||
-rw-r--r-- | hw/spapr_hcall.c | 44 | ||||
-rw-r--r-- | hw/spapr_rtas.c | 131 |
4 files changed, 222 insertions, 11 deletions
diff --git a/hw/spapr.c b/hw/spapr.c index 57140d231d..c07af19543 100644 --- a/hw/spapr.c +++ b/hw/spapr.c @@ -40,6 +40,7 @@ #define KERNEL_LOAD_ADDR 0x00000000 #define INITRD_LOAD_ADDR 0x02800000 #define FDT_MAX_SIZE 0x10000 +#define RTAS_MAX_SIZE 0x10000 #define TIMEBASE_FREQ 512000000ULL @@ -53,6 +54,8 @@ static void *spapr_create_fdt(int *fdt_size, ram_addr_t ramsize, target_phys_addr_t initrd_base, target_phys_addr_t initrd_size, const char *kernel_cmdline, + target_phys_addr_t rtas_addr, + target_phys_addr_t rtas_size, long hash_shift) { void *fdt; @@ -195,6 +198,12 @@ static void *spapr_create_fdt(int *fdt_size, ram_addr_t ramsize, exit(1); } + /* RTAS */ + ret = spapr_rtas_device_tree_setup(fdt, rtas_addr, rtas_size); + if (ret < 0) { + fprintf(stderr, "Couldn't set up RTAS device tree properties\n"); + } + _FDT((fdt_pack(fdt))); *fdt_size = fdt_totalsize(fdt); @@ -224,11 +233,12 @@ static void ppc_spapr_init(ram_addr_t ram_size, void *fdt, *htab; int i; ram_addr_t ram_offset; - target_phys_addr_t fdt_addr; + target_phys_addr_t fdt_addr, rtas_addr; uint32_t kernel_base, initrd_base; - long kernel_size, initrd_size, htab_size; + long kernel_size, initrd_size, htab_size, rtas_size; long pteg_shift = 17; int fdt_size; + char *filename; spapr = qemu_malloc(sizeof(*spapr)); cpu_ppc_hypercall = emulate_spapr_hypercall; @@ -237,6 +247,8 @@ static void ppc_spapr_init(ram_addr_t ram_size, * 2GB, so that it can be processed with 32-bit code if * necessary */ fdt_addr = MIN(ram_size, 0x80000000) - FDT_MAX_SIZE; + /* RTAS goes just below that */ + rtas_addr = fdt_addr - RTAS_MAX_SIZE; /* init CPUs */ if (cpu_model == NULL) { @@ -276,6 +288,14 @@ static void ppc_spapr_init(ram_addr_t ram_size, envs[i]->htab_mask = htab_size - 1; } + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "spapr-rtas.bin"); + rtas_size = load_image_targphys(filename, rtas_addr, ram_size - rtas_addr); + if (rtas_size < 0) { + hw_error("qemu: could not load LPAR rtas '%s'\n", filename); + exit(1); + } + qemu_free(filename); + spapr->vio_bus = spapr_vio_bus_init(); for (i = 0; i < MAX_SERIAL_PORTS; i++) { @@ -323,7 +343,7 @@ static void ppc_spapr_init(ram_addr_t ram_size, /* Prepare the device tree */ fdt = spapr_create_fdt(&fdt_size, ram_size, cpu_model, envs, spapr, initrd_base, initrd_size, kernel_cmdline, - pteg_shift + 7); + rtas_addr, rtas_size, pteg_shift + 7); assert(fdt != NULL); cpu_physical_memory_write(fdt_addr, fdt, fdt_size); diff --git a/hw/spapr.h b/hw/spapr.h index 06cca15b94..0dcb83a5ae 100644 --- a/hw/spapr.h +++ b/hw/spapr.h @@ -237,6 +237,18 @@ typedef struct sPAPREnvironment { #define H_GET_MPP 0x2D4 #define MAX_HCALL_OPCODE H_GET_MPP +/* The hcalls above are standardized in PAPR and implemented by pHyp + * as well. + * + * We also need some hcalls which are specific to qemu / KVM-on-POWER. + * So far we just need one for H_RTAS, but in future we'll need more + * for extensions like virtio. We put those into the 0xf000-0xfffc + * range which is reserved by PAPR for "platform-specific" hcalls. + */ +#define KVMPPC_HCALL_BASE 0xf000 +#define KVMPPC_H_RTAS (KVMPPC_HCALL_BASE + 0x0) +#define KVMPPC_HCALL_MAX KVMPPC_H_RTAS + extern sPAPREnvironment *spapr; /*#define DEBUG_SPAPR_HCALLS*/ @@ -257,4 +269,24 @@ void spapr_register_hypercall(target_ulong opcode, spapr_hcall_fn fn); target_ulong spapr_hypercall(CPUState *env, target_ulong opcode, target_ulong *args); +static inline uint32_t rtas_ld(target_ulong phys, int n) +{ + return ldl_phys(phys + 4*n); +} + +static inline void rtas_st(target_ulong phys, int n, uint32_t val) +{ + stl_phys(phys + 4*n, val); +} + +typedef void (*spapr_rtas_fn)(sPAPREnvironment *spapr, uint32_t token, + uint32_t nargs, target_ulong args, + uint32_t nret, target_ulong rets); +void spapr_rtas_register(const char *name, spapr_rtas_fn fn); +target_ulong spapr_rtas_call(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, target_ulong args, + uint32_t nret, target_ulong rets); +int spapr_rtas_device_tree_setup(void *fdt, target_phys_addr_t rtas_addr, + target_phys_addr_t rtas_size); + #endif /* !defined (__HW_SPAPR_H__) */ diff --git a/hw/spapr_hcall.c b/hw/spapr_hcall.c index 0ba17172da..d8c721e472 100644 --- a/hw/spapr_hcall.c +++ b/hw/spapr_hcall.c @@ -248,20 +248,38 @@ static target_ulong h_protect(CPUState *env, sPAPREnvironment *spapr, return H_SUCCESS; } -spapr_hcall_fn hypercall_table[(MAX_HCALL_OPCODE / 4) + 1]; +static target_ulong h_rtas(CPUState *env, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong rtas_r3 = args[0]; + uint32_t token = ldl_phys(rtas_r3); + uint32_t nargs = ldl_phys(rtas_r3 + 4); + uint32_t nret = ldl_phys(rtas_r3 + 8); + + return spapr_rtas_call(spapr, token, nargs, rtas_r3 + 12, + nret, rtas_r3 + 12 + 4*nargs); +} + +spapr_hcall_fn papr_hypercall_table[(MAX_HCALL_OPCODE / 4) + 1]; +spapr_hcall_fn kvmppc_hypercall_table[KVMPPC_HCALL_MAX - KVMPPC_HCALL_BASE]; void spapr_register_hypercall(target_ulong opcode, spapr_hcall_fn fn) { - spapr_hcall_fn old_fn; + spapr_hcall_fn *slot; + + if (opcode <= MAX_HCALL_OPCODE) { + assert((opcode & 0x3) == 0); - assert(opcode <= MAX_HCALL_OPCODE); - assert((opcode & 0x3) == 0); + slot = &papr_hypercall_table[opcode / 4]; + } else { + assert((opcode >= KVMPPC_HCALL_BASE) && (opcode <= KVMPPC_HCALL_MAX)); - old_fn = hypercall_table[opcode / 4]; - assert(!old_fn || (fn == old_fn)); + slot = &kvmppc_hypercall_table[opcode - KVMPPC_HCALL_BASE]; + } - hypercall_table[opcode / 4] = fn; + assert(!(*slot) || (fn == *slot)); + *slot = fn; } target_ulong spapr_hypercall(CPUState *env, target_ulong opcode, @@ -274,7 +292,14 @@ target_ulong spapr_hypercall(CPUState *env, target_ulong opcode, if ((opcode <= MAX_HCALL_OPCODE) && ((opcode & 0x3) == 0)) { - spapr_hcall_fn fn = hypercall_table[opcode / 4]; + spapr_hcall_fn fn = papr_hypercall_table[opcode / 4]; + + if (fn) { + return fn(env, spapr, opcode, args); + } + } else if ((opcode >= KVMPPC_HCALL_BASE) && + (opcode <= KVMPPC_HCALL_MAX)) { + spapr_hcall_fn fn = kvmppc_hypercall_table[opcode - KVMPPC_HCALL_BASE]; if (fn) { return fn(env, spapr, opcode, args); @@ -291,5 +316,8 @@ static void hypercall_init(void) spapr_register_hypercall(H_ENTER, h_enter); spapr_register_hypercall(H_REMOVE, h_remove); spapr_register_hypercall(H_PROTECT, h_protect); + + /* qemu/KVM-PPC specific hcalls */ + spapr_register_hypercall(KVMPPC_H_RTAS, h_rtas); } device_init(hypercall_init); diff --git a/hw/spapr_rtas.c b/hw/spapr_rtas.c new file mode 100644 index 0000000000..3f090f5518 --- /dev/null +++ b/hw/spapr_rtas.c @@ -0,0 +1,131 @@ +/* + * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator + * + * Hypercall based emulated RTAS + * + * Copyright (c) 2010-2011 David Gibson, IBM Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "cpu.h" +#include "sysemu.h" +#include "qemu-char.h" +#include "hw/qdev.h" +#include "device_tree.h" + +#include "hw/spapr.h" +#include "hw/spapr_vio.h" + +#include <libfdt.h> + +#define TOKEN_BASE 0x2000 +#define TOKEN_MAX 0x100 + +static struct rtas_call { + const char *name; + spapr_rtas_fn fn; +} rtas_table[TOKEN_MAX]; + +struct rtas_call *rtas_next = rtas_table; + +target_ulong spapr_rtas_call(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, target_ulong args, + uint32_t nret, target_ulong rets) +{ + if ((token >= TOKEN_BASE) + && ((token - TOKEN_BASE) < TOKEN_MAX)) { + struct rtas_call *call = rtas_table + (token - TOKEN_BASE); + + if (call->fn) { + call->fn(spapr, token, nargs, args, nret, rets); + return H_SUCCESS; + } + } + + hcall_dprintf("Unknown RTAS token 0x%x\n", token); + rtas_st(rets, 0, -3); + return H_PARAMETER; +} + +void spapr_rtas_register(const char *name, spapr_rtas_fn fn) +{ + assert(rtas_next < (rtas_table + TOKEN_MAX)); + + rtas_next->name = name; + rtas_next->fn = fn; + + rtas_next++; +} + +int spapr_rtas_device_tree_setup(void *fdt, target_phys_addr_t rtas_addr, + target_phys_addr_t rtas_size) +{ + int ret; + int i; + + ret = fdt_add_mem_rsv(fdt, rtas_addr, rtas_size); + if (ret < 0) { + fprintf(stderr, "Couldn't add RTAS reserve entry: %s\n", + fdt_strerror(ret)); + return ret; + } + + ret = qemu_devtree_setprop_cell(fdt, "/rtas", "linux,rtas-base", + rtas_addr); + if (ret < 0) { + fprintf(stderr, "Couldn't add linux,rtas-base property: %s\n", + fdt_strerror(ret)); + return ret; + } + + ret = qemu_devtree_setprop_cell(fdt, "/rtas", "linux,rtas-entry", + rtas_addr); + if (ret < 0) { + fprintf(stderr, "Couldn't add linux,rtas-entry property: %s\n", + fdt_strerror(ret)); + return ret; + } + + ret = qemu_devtree_setprop_cell(fdt, "/rtas", "rtas-size", + rtas_size); + if (ret < 0) { + fprintf(stderr, "Couldn't add rtas-size property: %s\n", + fdt_strerror(ret)); + return ret; + } + + for (i = 0; i < TOKEN_MAX; i++) { + struct rtas_call *call = &rtas_table[i]; + + if (!call->fn) { + continue; + } + + ret = qemu_devtree_setprop_cell(fdt, "/rtas", call->name, + i + TOKEN_BASE); + if (ret < 0) { + fprintf(stderr, "Couldn't add rtas token for %s: %s\n", + call->name, fdt_strerror(ret)); + return ret; + } + + } + return 0; +} |