From: David Gibson <da...@gibson.dropbear.id.au> 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 <d...@au1.ibm.com> --- Makefile | 3 +- Makefile.target | 2 +- hw/spapr.c | 27 +++++++++++-- hw/spapr.h | 21 ++++++++++ hw/spapr_hcall.c | 15 +++++++ hw/spapr_rtas.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ pc-bios/spapr-rtas.bin | Bin 0 -> 20 bytes 7 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 hw/spapr_rtas.c create mode 100644 pc-bios/spapr-rtas.bin diff --git a/Makefile b/Makefile index eca4c76..fc4bd24 100644 --- a/Makefile +++ b/Makefile @@ -213,7 +213,8 @@ pxe-ne2k_pci.bin pxe-pcnet.bin \ pxe-rtl8139.bin pxe-virtio.bin \ bamboo.dtb petalogix-s3adsp1800.dtb \ multiboot.bin linuxboot.bin \ -s390-zipl.rom +s390-zipl.rom \ +spapr-rtas.bin else BLOBS= endif diff --git a/Makefile.target b/Makefile.target index ba243bf..fa59109 100644 --- a/Makefile.target +++ b/Makefile.target @@ -232,7 +232,7 @@ obj-ppc-y += ppc_oldworld.o # NewWorld PowerMac obj-ppc-y += ppc_newworld.o # IBM pSeries (sPAPR) -obj-ppc-y += spapr.o spapr_hcall.o spapr_vio.o +obj-ppc-y += spapr.o spapr_hcall.o spapr_rtas.o spapr_vio.o obj-ppc-y += spapr_vty.o # PowerPC 4xx boards obj-ppc-y += ppc4xx_devs.o ppc4xx_pci.o ppc405_uc.o ppc405_boards.o diff --git a/hw/spapr.c b/hw/spapr.c index c3d9286..f41451b 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 @@ -51,6 +52,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; @@ -162,7 +165,7 @@ static void *spapr_create_fdt(int *fdt_size, ram_addr_t ramsize, _FDT((fdt_property(fdt, "ibm,hypertas-functions", hypertas_prop, sizeof(hypertas_prop)))); - + _FDT((fdt_end_node(fdt))); /* vdevice */ @@ -186,6 +189,11 @@ static void *spapr_create_fdt(int *fdt_size, ram_addr_t ramsize, fprintf(stderr, "couldn't setup vio devices in fdt\n"); } + /* 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))); if (fdt_size) { @@ -218,12 +226,13 @@ 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; sPAPREnvironment *spapr; + char *filename; spapr = qemu_malloc(sizeof(*spapr)); @@ -231,6 +240,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) { @@ -271,6 +282,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++) { @@ -317,7 +336,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); if (!fdt) { hw_error("Couldn't create pSeries device tree\n"); exit(1); diff --git a/hw/spapr.h b/hw/spapr.h index 47bf2ef..7a7c319 100644 --- a/hw/spapr.h +++ b/hw/spapr.h @@ -237,6 +237,8 @@ typedef struct sPAPREnvironment { #define H_GET_MPP 0x2D4 #define MAX_HCALL_OPCODE H_GET_MPP +#define H_RTAS 0x72746173 + typedef target_ulong (*spapr_hcall_fn)(CPUState *env, sPAPREnvironment *spapr, target_ulong opcode, target_ulong *args); @@ -245,5 +247,24 @@ void spapr_register_hypercall(target_ulong opcode, spapr_hcall_fn fn); target_ulong spapr_hypercall(CPUState *env, sPAPREnvironment *spapr, 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 2b14000..7b8e17c 100644 --- a/hw/spapr_hcall.c +++ b/hw/spapr_hcall.c @@ -241,6 +241,16 @@ static target_ulong h_protect(CPUState *env, sPAPREnvironment *spapr, return H_SUCCESS; } +static target_ulong h_rtas(sPAPREnvironment *spapr, target_ulong rtas_r3) +{ + 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); +} + struct hypercall { spapr_hcall_fn fn; } hypercall_table[(MAX_HCALL_OPCODE / 4) + 1]; @@ -276,6 +286,11 @@ target_ulong spapr_hypercall(CPUState *env, sPAPREnvironment *spapr, return hc->fn(env, spapr, opcode, args); } + if (opcode == H_RTAS) { + /* H_RTAS is a special case outside the normal range */ + return h_rtas(spapr, args[0]); + } + fprintf(stderr, "Unimplemented hcall 0x" TARGET_FMT_lx "\n", opcode); return H_FUNCTION; } diff --git a/hw/spapr_rtas.c b/hw/spapr_rtas.c new file mode 100644 index 0000000..c606018 --- /dev/null +++ b/hw/spapr_rtas.c @@ -0,0 +1,104 @@ +#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; + } + } + + fprintf(stderr, "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; +} diff --git a/pc-bios/spapr-rtas.bin b/pc-bios/spapr-rtas.bin new file mode 100644 index 0000000000000000000000000000000000000000..eade9c0e8ff0fd3071e3a6638a11c1a2e9a47152 GIT binary patch literal 20 bcmb<Pk*=^wC@M)vPAqm|U{LaFU{C-6M#cr< literal 0 HcmV?d00001 -- 1.7.1