From patchwork Thu Feb 20 06:16:18 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexey Kardashevskiy X-Patchwork-Id: 1241231 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=ozlabs.ru Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 48NPWJ5MwMz9sRs for ; Thu, 20 Feb 2020 17:17:00 +1100 (AEDT) Received: from localhost ([::1]:36660 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4f8s-0007ro-Ll for incoming@patchwork.ozlabs.org; Thu, 20 Feb 2020 01:16:58 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:56234) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4f8V-0007qk-35 for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:16:37 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j4f8T-00055E-M7 for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:16:34 -0500 Received: from [107.174.27.60] (port=52474 helo=ozlabs.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1j4f8T-00054q-Er; Thu, 20 Feb 2020 01:16:33 -0500 Received: from fstn1-p1.ozlabs.ibm.com (localhost [IPv6:::1]) by ozlabs.ru (Postfix) with ESMTP id 9DE82AE80110; Thu, 20 Feb 2020 01:14:58 -0500 (EST) From: Alexey Kardashevskiy To: qemu-devel@nongnu.org Subject: [PATCH qemu v7 1/5] ppc/spapr: Move GPRs setup to one place Date: Thu, 20 Feb 2020 17:16:18 +1100 Message-Id: <20200220061622.15064-2-aik@ozlabs.ru> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200220061622.15064-1-aik@ozlabs.ru> References: <20200220061622.15064-1-aik@ozlabs.ru> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-Received-From: 107.174.27.60 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Alexey Kardashevskiy , Paolo Bonzini , qemu-ppc@nongnu.org, David Gibson Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" At the moment "pseries" starts in SLOF which only expects the FDT blob pointer in r3. As we are going to introduce a OpenFirmware support in QEMU, we will be booting OF clients directly and these expect a stack pointer in r1, Linux looks at r3/r4 for the initramdisk location (although vmlinux can find this from the device tree but zImage from distro kernels cannot). This extends spapr_cpu_set_entry_state() to take more registers. This should cause no behavioral change. Signed-off-by: Alexey Kardashevskiy --- Changes: v7: * removed r5 as it points to prom entry which is now provided by a new firmware in later patches --- include/hw/ppc/spapr_cpu_core.h | 4 +++- hw/ppc/spapr.c | 2 +- hw/ppc/spapr_cpu_core.c | 6 +++++- hw/ppc/spapr_rtas.c | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/hw/ppc/spapr_cpu_core.h b/include/hw/ppc/spapr_cpu_core.h index 1c4cc6559c52..7aed8f555b4f 100644 --- a/include/hw/ppc/spapr_cpu_core.h +++ b/include/hw/ppc/spapr_cpu_core.h @@ -40,7 +40,9 @@ typedef struct SpaprCpuCoreClass { } SpaprCpuCoreClass; const char *spapr_get_cpu_core_type(const char *cpu_type); -void spapr_cpu_set_entry_state(PowerPCCPU *cpu, target_ulong nip, target_ulong r3); +void spapr_cpu_set_entry_state(PowerPCCPU *cpu, target_ulong nip, + target_ulong r1, target_ulong r3, + target_ulong r4); typedef struct SpaprCpuState { uint64_t vpa_addr; diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 828e2cc1359a..90b68e2f479e 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -1691,7 +1691,7 @@ static void spapr_machine_reset(MachineState *machine) spapr->fdt_blob = fdt; /* Set up the entry state */ - spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT, fdt_addr); + spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT, 0, fdt_addr, 0); first_ppc_cpu->env.gpr[5] = 0; spapr->cas_reboot = false; diff --git a/hw/ppc/spapr_cpu_core.c b/hw/ppc/spapr_cpu_core.c index d09125d9afd4..590bd70e05cc 100644 --- a/hw/ppc/spapr_cpu_core.c +++ b/hw/ppc/spapr_cpu_core.c @@ -84,13 +84,17 @@ static void spapr_reset_vcpu(PowerPCCPU *cpu) spapr_irq_cpu_intc_reset(spapr, cpu); } -void spapr_cpu_set_entry_state(PowerPCCPU *cpu, target_ulong nip, target_ulong r3) +void spapr_cpu_set_entry_state(PowerPCCPU *cpu, target_ulong nip, + target_ulong r1, target_ulong r3, + target_ulong r4) { PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cpu); CPUPPCState *env = &cpu->env; env->nip = nip; + env->gpr[1] = r1; env->gpr[3] = r3; + env->gpr[4] = r4; kvmppc_set_reg_ppc_online(cpu, 1); CPU(cpu)->halted = 0; /* Enable Power-saving mode Exit Cause exceptions */ diff --git a/hw/ppc/spapr_rtas.c b/hw/ppc/spapr_rtas.c index 656fdd221665..fe83b50c6629 100644 --- a/hw/ppc/spapr_rtas.c +++ b/hw/ppc/spapr_rtas.c @@ -190,7 +190,7 @@ static void rtas_start_cpu(PowerPCCPU *callcpu, SpaprMachineState *spapr, */ newcpu->env.tb_env->tb_offset = callcpu->env.tb_env->tb_offset; - spapr_cpu_set_entry_state(newcpu, start, r3); + spapr_cpu_set_entry_state(newcpu, start, 0, r3, 0); qemu_cpu_kick(CPU(newcpu)); From patchwork Thu Feb 20 06:16:19 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexey Kardashevskiy X-Patchwork-Id: 1241235 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=ozlabs.ru Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 48NPZq2RH5z9sRm for ; Thu, 20 Feb 2020 17:20:03 +1100 (AEDT) Received: from localhost ([::1]:36712 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4fBp-0004hH-9H for incoming@patchwork.ozlabs.org; Thu, 20 Feb 2020 01:20:01 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:56390) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4f8z-0000Gy-47 for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:17:06 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j4f8y-0005FI-6h for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:17:05 -0500 Received: from [107.174.27.60] (port=52622 helo=ozlabs.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1j4f8y-0005FA-1Y; Thu, 20 Feb 2020 01:17:04 -0500 Received: from fstn1-p1.ozlabs.ibm.com (localhost [IPv6:::1]) by ozlabs.ru (Postfix) with ESMTP id EDF16AE8056B; Thu, 20 Feb 2020 01:14:59 -0500 (EST) From: Alexey Kardashevskiy To: qemu-devel@nongnu.org Subject: [PATCH qemu v7 2/5] spapr/spapr: Make vty_getchars public Date: Thu, 20 Feb 2020 17:16:19 +1100 Message-Id: <20200220061622.15064-3-aik@ozlabs.ru> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200220061622.15064-1-aik@ozlabs.ru> References: <20200220061622.15064-1-aik@ozlabs.ru> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-Received-From: 107.174.27.60 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Alexey Kardashevskiy , Paolo Bonzini , qemu-ppc@nongnu.org, David Gibson Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" A serial device fetches the data from the chardev backend as soon as input happens and stores it in its internal device specific buffer, every char device implements it again. Since there is no unified interface to read such buffer, we will have to read characters directly from VIO_SPAPR_VTY_DEVICE. The OF client is going to need this. Signed-off-by: Alexey Kardashevskiy --- include/hw/ppc/spapr_vio.h | 1 + hw/char/spapr_vty.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/hw/ppc/spapr_vio.h b/include/hw/ppc/spapr_vio.h index bed7df60e35c..77e9b73bdfe0 100644 --- a/include/hw/ppc/spapr_vio.h +++ b/include/hw/ppc/spapr_vio.h @@ -130,6 +130,7 @@ int spapr_vio_send_crq(SpaprVioDevice *dev, uint8_t *crq); SpaprVioDevice *vty_lookup(SpaprMachineState *spapr, target_ulong reg); void vty_putchars(SpaprVioDevice *sdev, uint8_t *buf, int len); +int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max); void spapr_vty_create(SpaprVioBus *bus, Chardev *chardev); void spapr_vlan_create(SpaprVioBus *bus, NICInfo *nd); void spapr_vscsi_create(SpaprVioBus *bus); diff --git a/hw/char/spapr_vty.c b/hw/char/spapr_vty.c index ecb94f5673ca..1c00da75b4f1 100644 --- a/hw/char/spapr_vty.c +++ b/hw/char/spapr_vty.c @@ -52,7 +52,7 @@ static void vty_receive(void *opaque, const uint8_t *buf, int size) } } -static int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max) +int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max) { SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(sdev); int n = 0; From patchwork Thu Feb 20 06:16:20 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexey Kardashevskiy X-Patchwork-Id: 1241233 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=ozlabs.ru Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 48NPYJ1Y0tz9sRm for ; Thu, 20 Feb 2020 17:18:44 +1100 (AEDT) Received: from localhost ([::1]:36686 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4fAY-0002PP-4B for incoming@patchwork.ozlabs.org; Thu, 20 Feb 2020 01:18:42 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:56281) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4f8Y-0007r3-49 for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:16:39 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j4f8V-00056I-O0 for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:16:38 -0500 Received: from [107.174.27.60] (port=52522 helo=ozlabs.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1j4f8V-000569-Fi; Thu, 20 Feb 2020 01:16:35 -0500 Received: from fstn1-p1.ozlabs.ibm.com (localhost [IPv6:::1]) by ozlabs.ru (Postfix) with ESMTP id 48C35AE8056D; Thu, 20 Feb 2020 01:15:01 -0500 (EST) From: Alexey Kardashevskiy To: qemu-devel@nongnu.org Subject: [PATCH qemu v7 3/5] spapr/cas: Separate CAS handling from rebuilding the FDT Date: Thu, 20 Feb 2020 17:16:20 +1100 Message-Id: <20200220061622.15064-4-aik@ozlabs.ru> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200220061622.15064-1-aik@ozlabs.ru> References: <20200220061622.15064-1-aik@ozlabs.ru> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-Received-From: 107.174.27.60 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Alexey Kardashevskiy , Paolo Bonzini , qemu-ppc@nongnu.org, David Gibson Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" At the moment "ibm,client-architecture-support" ("CAS") is implemented in SLOF and QEMU assists via the custom H_CAS hypercall which copies an updated flatten device tree (FDT) blob to the SLOF memory which it then uses to update its internal tree. When we enable the OpenFirmware client interface in QEMU, we won't need to copy the FDT to the guest as the client is expected to fetch the device tree using the client interface. This moves FDT rebuild out to a separate helper which is going to be called from the "ibm,client-architecture-support" handler and leaves writing FDT to the guest in the H_CAS handler. This should not cause any behavioral change. Signed-off-by: Alexey Kardashevskiy --- include/hw/ppc/spapr.h | 7 +++++ hw/ppc/spapr.c | 1 - hw/ppc/spapr_hcall.c | 67 ++++++++++++++++++++++++++---------------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/include/hw/ppc/spapr.h b/include/hw/ppc/spapr.h index 09110961a589..7802acee0c85 100644 --- a/include/hw/ppc/spapr.h +++ b/include/hw/ppc/spapr.h @@ -102,6 +102,8 @@ typedef enum { #define SPAPR_CAP_FIXED_CCD 0x03 #define SPAPR_CAP_FIXED_NA 0x10 /* Lets leave a bit of a gap... */ +#define FDT_MAX_SIZE 0x100000 + typedef struct SpaprCapabilities SpaprCapabilities; struct SpaprCapabilities { uint8_t caps[SPAPR_CAP_NUM]; @@ -558,6 +560,11 @@ void spapr_register_hypercall(target_ulong opcode, spapr_hcall_fn fn); target_ulong spapr_hypercall(PowerPCCPU *cpu, target_ulong opcode, target_ulong *args); +target_ulong do_client_architecture_support(PowerPCCPU *cpu, + SpaprMachineState *spapr, + target_ulong addr, + target_ulong fdt_bufsize); + /* Virtual Processor Area structure constants */ #define VPA_MIN_SIZE 640 #define VPA_SIZE_OFFSET 0x4 diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 90b68e2f479e..62d6487c2568 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -96,7 +96,6 @@ * * We load our kernel at 4M, leaving space for SLOF initial image */ -#define FDT_MAX_SIZE 0x100000 #define RTAS_MAX_ADDR 0x80000000 /* RTAS must stay below that */ #define FW_MAX_SIZE 0x400000 #define FW_FILE_NAME "slof.bin" diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c index 6db3dbde9c92..35de3ab95f42 100644 --- a/hw/ppc/spapr_hcall.c +++ b/hw/ppc/spapr_hcall.c @@ -1664,16 +1664,12 @@ static bool spapr_transient_dev_before_cas(void) return false; } -static target_ulong h_client_architecture_support(PowerPCCPU *cpu, - SpaprMachineState *spapr, - target_ulong opcode, - target_ulong *args) +target_ulong do_client_architecture_support(PowerPCCPU *cpu, + SpaprMachineState *spapr, + target_ulong vec, + target_ulong fdt_bufsize) { - /* Working address in data buffer */ - target_ulong addr = ppc64_phys_to_real(args[0]); - target_ulong fdt_buf = args[1]; - target_ulong fdt_bufsize = args[2]; - target_ulong ov_table; + target_ulong ov_table; /* Working address in data buffer */ uint32_t cas_pvr; SpaprOptionVector *ov1_guest, *ov5_guest, *ov5_cas_old; bool guest_radix; @@ -1693,7 +1689,7 @@ static target_ulong h_client_architecture_support(PowerPCCPU *cpu, } } - cas_pvr = cas_check_pvr(spapr, cpu, &addr, &raw_mode_supported, &local_err); + cas_pvr = cas_check_pvr(spapr, cpu, &vec, &raw_mode_supported, &local_err); if (local_err) { error_report_err(local_err); return H_HARDWARE; @@ -1716,7 +1712,7 @@ static target_ulong h_client_architecture_support(PowerPCCPU *cpu, } /* For the future use: here @ov_table points to the first option vector */ - ov_table = addr; + ov_table = vec; ov1_guest = spapr_ovec_parse_vector(ov_table, 1); if (!ov1_guest) { @@ -1840,7 +1836,6 @@ static target_ulong h_client_architecture_support(PowerPCCPU *cpu, if (!spapr->cas_reboot) { void *fdt; - SpaprDeviceTreeUpdateHeader hdr = { .version_id = 1 }; /* If spapr_machine_reset() did not set up a HPT but one is necessary * (because the guest isn't going to use radix) then set it up here. */ @@ -1849,21 +1844,7 @@ static target_ulong h_client_architecture_support(PowerPCCPU *cpu, spapr_setup_hpt_and_vrma(spapr); } - if (fdt_bufsize < sizeof(hdr)) { - error_report("SLOF provided insufficient CAS buffer " - TARGET_FMT_lu " (min: %zu)", fdt_bufsize, sizeof(hdr)); - exit(EXIT_FAILURE); - } - - fdt_bufsize -= sizeof(hdr); - fdt = spapr_build_fdt(spapr, false, fdt_bufsize); - _FDT((fdt_pack(fdt))); - - cpu_physical_memory_write(fdt_buf, &hdr, sizeof(hdr)); - cpu_physical_memory_write(fdt_buf + sizeof(hdr), fdt, - fdt_totalsize(fdt)); - trace_spapr_cas_continue(fdt_totalsize(fdt) + sizeof(hdr)); g_free(spapr->fdt_blob); spapr->fdt_size = fdt_totalsize(fdt); @@ -1878,6 +1859,40 @@ static target_ulong h_client_architecture_support(PowerPCCPU *cpu, return H_SUCCESS; } +static target_ulong h_client_architecture_support(PowerPCCPU *cpu, + SpaprMachineState *spapr, + target_ulong opcode, + target_ulong *args) +{ + target_ulong vec = ppc64_phys_to_real(args[0]); + target_ulong fdt_buf = args[1]; + target_ulong fdt_bufsize = args[2]; + target_ulong ret; + SpaprDeviceTreeUpdateHeader hdr = { .version_id = 1 }; + + if (fdt_bufsize < sizeof(hdr)) { + error_report("SLOF provided insufficient CAS buffer " + TARGET_FMT_lu " (min: %zu)", fdt_bufsize, sizeof(hdr)); + exit(EXIT_FAILURE); + } + + fdt_bufsize -= sizeof(hdr); + + ret = do_client_architecture_support(cpu, spapr, vec, fdt_bufsize); + if (ret == H_SUCCESS) { + _FDT((fdt_pack(spapr->fdt_blob))); + spapr->fdt_size = fdt_totalsize(spapr->fdt_blob); + spapr->fdt_initial_size = spapr->fdt_size; + + cpu_physical_memory_write(fdt_buf, &hdr, sizeof(hdr)); + cpu_physical_memory_write(fdt_buf + sizeof(hdr), spapr->fdt_blob, + spapr->fdt_size); + trace_spapr_cas_continue(spapr->fdt_size + sizeof(hdr)); + } + + return ret; +} + static target_ulong h_home_node_associativity(PowerPCCPU *cpu, SpaprMachineState *spapr, target_ulong opcode, From patchwork Thu Feb 20 06:16:21 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alexey Kardashevskiy X-Patchwork-Id: 1241234 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=ozlabs.ru Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 48NPYQ3wkJz9sRm for ; Thu, 20 Feb 2020 17:18:50 +1100 (AEDT) Received: from localhost ([::1]:36690 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4fAe-0002YO-G1 for incoming@patchwork.ozlabs.org; Thu, 20 Feb 2020 01:18:48 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:56337) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4f8e-00081u-OZ for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:16:50 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j4f8Y-00057k-5d for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:16:44 -0500 Received: from [107.174.27.60] (port=52522 helo=ozlabs.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1j4f8X-000569-Jv; Thu, 20 Feb 2020 01:16:38 -0500 Received: from fstn1-p1.ozlabs.ibm.com (localhost [IPv6:::1]) by ozlabs.ru (Postfix) with ESMTP id BF5F4AE80570; Thu, 20 Feb 2020 01:15:02 -0500 (EST) From: Alexey Kardashevskiy To: qemu-devel@nongnu.org Subject: [PATCH qemu v7 4/5] spapr: Implement Open Firmware client interface Date: Thu, 20 Feb 2020 17:16:21 +1100 Message-Id: <20200220061622.15064-5-aik@ozlabs.ru> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200220061622.15064-1-aik@ozlabs.ru> References: <20200220061622.15064-1-aik@ozlabs.ru> MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-Received-From: 107.174.27.60 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Alexey Kardashevskiy , Paolo Bonzini , qemu-ppc@nongnu.org, David Gibson Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" The PAPR platform which describes an OS environment that's presented by a combination of a hypervisor and firmware. The features it specifies require collaboration between the firmware and the hypervisor. Since the beginning, the runtime component of the firmware (RTAS) has been implemented as a 20 byte shim which simply forwards it to a hypercall implemented in qemu. The boottime firmware component is SLOF - but a build that's specific to qemu, and has always needed to be updated in sync with it. Even though we've managed to limit the amount of runtime communication we need between qemu and SLOF, there's some, and it's become increasingly awkward to handle as we've implemented new features. This implements a boot time OF client interface (CI) which is enabled by a new "vof" pseries machine option (stands for "Virtual Open Firmware). When enabled, QEMU implements the custom H_OF_CLIENT hcall which implements Open Firmware Client Interface (OF CI). This allows using a smaller stateless firmware which does not have to manage the device tree. The new "vof.bin" firmware image is included with source code under pc-bios/. It also includes RTAS blob. This adds support for a console. For output any serial device can be used, for stdin the support is limited by spapr-vty only as allowing input from a serial device requires device-model specific code (output is simpler). This implements a handful of CI methods just to get Linux and GRUB going; Linux requires even less. In particular, this implements the device tree fetching, reading from block device, read-write stdout/stdin and ibm,client-architecture-support. This implements changing some device tree properties which we know how to deal with, the rest is ignored. To allow changes, this skips fdt_pack() when vof=on as not packing the blob leaves some room for appending. In absence of SLOF, this assigns "phandles" to device tree nodes to make device tree traversing work. When vof=on, this adds "/chosen" every time QEMU (re)builds a tree. This implements "claim" (an OF CI memory allocator) and updates "/memory@0/available" to report the client about available memory. This adds basic instances support which are managed by a hashmap ihandle -> [phandle, DeviceState, CharBackend]. Before the guest started, the used memory is: 0..4000 - the initial firmware 10000..180000 - stack This OF CI does not implement "interpret". With this basic support, this can only boot into kernel directly. However this is just enough for the petitboot kernel and initradmdisk to boot from any possible source. Signed-off-by: Alexey Kardashevskiy --- Changes: v7: * now we have a small firmware which loads at 0 as SLOF and starts from 0x100 as SLOF * no MBR/ELF/GRUB business in QEMU anymore * blockdev is a separate patch * networking is a separate patch v6: * borrowed a big chunk of commit log introduction from David * fixed initial stack pointer (points to the highest address of stack) * traces for "interpret" and others * disabled translate_kernel_address() hack so grub can load (work in progress) * added "milliseconds" for grub * fixed "claim" allocator again * moved FDT_MAX_SIZE to spapr.h as spapr_of_client.c wants it too for CAS * moved the most code possible from spapr.c to spapr_of_client.c, such as RTAS, prom entry and FDT build/finalize * separated blobs * GRUB now proceeds to its console prompt (there are still other issues) * parse MBR/GPT to find PReP and load GRUB v5: * made instances keep device and chardev pointers * removed VIO dependencies * print error if RTAS memory is not claimed as it should have been * pack FDT as "quiesce" v4: * fixed open * validate ihandles in "call-method" v3: * fixed phandles allocation * s/__be32/uint32_t/ as we do not normally have __be32 type in qemu * fixed size of /chosen/stdout * bunch of renames * do not create rtas properties at all, let the client deal with it; instead setprop allows changing these in the FDT * no more packing FDT when bios=off - nobody needs it and getprop does not work otherwise * allow updating initramdisk device tree properties (for zImage) * added instances * fixed stdout on OF's "write" * removed special handling for stdout in OF client, spapr-vty handles it instead v2: * fixed claim() * added "setprop" * cleaner client interface and RTAS blobs management * boots to petitboot and further to the target system * more trace points --- hw/ppc/Makefile.objs | 1 + pc-bios/vof/Makefile | 18 + include/hw/ppc/spapr.h | 20 +- pc-bios/vof/vof.h | 48 ++ hw/ppc/spapr.c | 68 ++- hw/ppc/spapr_hcall.c | 6 +- hw/ppc/spapr_of_client.c | 1221 ++++++++++++++++++++++++++++++++++++++ pc-bios/vof/bootmem.c | 13 + pc-bios/vof/ci.c | 136 +++++ pc-bios/vof/libc.c | 91 +++ pc-bios/vof/main.c | 23 + hw/ppc/trace-events | 21 + pc-bios/README | 2 + pc-bios/vof.bin | Bin 0 -> 4272 bytes pc-bios/vof/entry.S | 58 ++ pc-bios/vof/l.lds | 48 ++ 16 files changed, 1760 insertions(+), 14 deletions(-) create mode 100644 pc-bios/vof/Makefile create mode 100644 pc-bios/vof/vof.h create mode 100644 hw/ppc/spapr_of_client.c create mode 100644 pc-bios/vof/bootmem.c create mode 100644 pc-bios/vof/ci.c create mode 100644 pc-bios/vof/libc.c create mode 100644 pc-bios/vof/main.c create mode 100755 pc-bios/vof.bin create mode 100644 pc-bios/vof/entry.S create mode 100644 pc-bios/vof/l.lds diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs index c3d3cc56eb51..70aa7918ba8a 100644 --- a/hw/ppc/Makefile.objs +++ b/hw/ppc/Makefile.objs @@ -8,6 +8,7 @@ obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_iommu.o spapr_rtas.o obj-$(CONFIG_PSERIES) += spapr_pci.o spapr_rtc.o spapr_drc.o obj-$(CONFIG_PSERIES) += spapr_cpu_core.o spapr_ovec.o spapr_irq.o obj-$(CONFIG_PSERIES) += spapr_tpm_proxy.o spapr_nvdimm.o +obj-$(CONFIG_PSERIES) += spapr_of_client.o obj-$(CONFIG_SPAPR_RNG) += spapr_rng.o obj-$(call land,$(CONFIG_PSERIES),$(CONFIG_LINUX)) += spapr_pci_vfio.o spapr_pci_nvlink2.o # IBM PowerNV diff --git a/pc-bios/vof/Makefile b/pc-bios/vof/Makefile new file mode 100644 index 000000000000..49f7e240eeff --- /dev/null +++ b/pc-bios/vof/Makefile @@ -0,0 +1,18 @@ +all: build-all + +build-all: vof.bin + +%.o: %.S + cc -m32 -mbig-endian -c -o $@ $< + +%.o: %.c + cc -m32 -mbig-endian -c -fno-stack-protector -Wno-builtin-declaration-mismatch -o $@ $< + +vof.elf: entry.o main.o libc.o ci.o bootmem.o + ld -nostdlib -e_start -Tl.lds -EB -o $@ $^ + +%.bin: %.elf + objcopy -O binary -j .text -j .data -j .toc -j .got2 $^ $@ + +clean: + rm -f *.o *.bin *.elf *~ diff --git a/include/hw/ppc/spapr.h b/include/hw/ppc/spapr.h index 7802acee0c85..5ef4c865695a 100644 --- a/include/hw/ppc/spapr.h +++ b/include/hw/ppc/spapr.h @@ -109,6 +109,11 @@ struct SpaprCapabilities { uint8_t caps[SPAPR_CAP_NUM]; }; +typedef struct { + uint64_t start; + uint64_t size; +} SpaprOfClaimed; + /** * SpaprMachineClass: */ @@ -165,6 +170,14 @@ struct SpaprMachineState { long kernel_size; bool kernel_le; uint64_t kernel_addr; + bool vof; /* Virtual Open Firmware */ + uint32_t rtas_base; + GArray *claimed; /* array of SpaprOfClaimed */ + uint64_t claimed_base; + GHashTable *of_instances; /* ihandle -> SpaprOfInstance */ + uint32_t of_instance_last; + Notifier machine_ready; + char *bootargs; uint32_t initrd_base; long initrd_size; uint64_t rtc_offset; /* Now used only during incoming migration */ @@ -532,7 +545,8 @@ struct SpaprMachineState { /* Client Architecture support */ #define KVMPPC_H_CAS (KVMPPC_HCALL_BASE + 0x2) #define KVMPPC_H_UPDATE_DT (KVMPPC_HCALL_BASE + 0x3) -#define KVMPPC_HCALL_MAX KVMPPC_H_UPDATE_DT +#define KVMPPC_H_OF_CLIENT (KVMPPC_HCALL_BASE + 0x5) +#define KVMPPC_HCALL_MAX KVMPPC_H_OF_CLIENT /* * The hcall range 0xEF00 to 0xEF80 is reserved for use in facilitating @@ -801,6 +815,10 @@ struct SpaprEventLogEntry { void *spapr_build_fdt(SpaprMachineState *spapr, bool reset, size_t space); void spapr_events_init(SpaprMachineState *sm); void spapr_dt_events(SpaprMachineState *sm, void *fdt); +void spapr_setup_of_client(SpaprMachineState *spapr, target_ulong *stack_ptr); +void spapr_of_client_dt(SpaprMachineState *spapr, void *fdt); +void spapr_of_client_dt_finalize(SpaprMachineState *spapr); +void spapr_of_client_machine_init(SpaprMachineState *spapr); void close_htab_fd(SpaprMachineState *spapr); void spapr_setup_hpt_and_vrma(SpaprMachineState *spapr); void spapr_free_hpt(SpaprMachineState *spapr); diff --git a/pc-bios/vof/vof.h b/pc-bios/vof/vof.h new file mode 100644 index 000000000000..738b2539aa19 --- /dev/null +++ b/pc-bios/vof/vof.h @@ -0,0 +1,48 @@ +#include + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned long uint32_t; +typedef unsigned long long uint64_t; +#define NULL (0) +#define PROM_ERROR (-1u) +typedef unsigned char bool; +typedef unsigned long ihandle; +typedef unsigned long phandle; +#define false ((bool)0) +#define true ((bool)1) +typedef int size_t; +typedef void client(void); + +/* globals */ +extern void _prom_entry(void); /* OF CI entry point (i.e. this firmware) */ + +void do_boot(unsigned long addr, unsigned long r3, unsigned long r4); + +/* libc */ +int strlen(const char *s); +int strcmp(const char *s1, const char *s2); +void *memcpy(void *dest, const void *src, size_t n); +int memcmp(const void *ptr1, const void *ptr2, size_t n); +void *memmove(void *dest, const void *src, size_t n); +void *memset(void *dest, int c, size_t size); + +/* Prom */ +typedef unsigned long prom_arg_t; +int call_prom(const char *service, int nargs, int nret, ...); + +/* CI wrappers */ +void ci_panic(const char *str); +phandle ci_finddevice(const char *path); +uint32_t ci_getprop(phandle ph, const char *propname, void *prop, int len); +ihandle ci_open(const char *path); +void ci_close(ihandle ih); +uint32_t ci_read(ihandle ih, void *buf, int len); +uint32_t ci_write(ihandle ih, const void *buf, int len); +void ci_stdout(const char *buf); +void ci_stdoutn(const char *buf, int len); +void *ci_claim(void *virt, uint32_t size, uint32_t align); +uint32_t ci_release(void *virt, uint32_t size); + +/* booting from -kernel */ +void boot_from_memory(uint64_t initrd, uint64_t initrdsize); diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 62d6487c2568..0c6d11206e73 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -99,6 +99,7 @@ #define RTAS_MAX_ADDR 0x80000000 /* RTAS must stay below that */ #define FW_MAX_SIZE 0x400000 #define FW_FILE_NAME "slof.bin" +#define FW_FILE_NAME_VOF "vof.bin" #define FW_OVERHEAD 0x2800000 #define KERNEL_LOAD_ADDR FW_MAX_SIZE @@ -1279,6 +1280,9 @@ void *spapr_build_fdt(SpaprMachineState *spapr, bool reset, size_t space) if (mc->nvdimm_supported) { spapr_dt_persistent_memory(fdt); } + if (spapr->vof) { + spapr_of_client_dt(spapr, fdt); + } return fdt; } @@ -1674,24 +1678,41 @@ static void spapr_machine_reset(MachineState *machine) */ fdt_addr = MIN(spapr->rma_size, RTAS_MAX_ADDR) - FDT_MAX_SIZE; + /* Set up the entry state */ + first_ppc_cpu->env.gpr[5] = 0; + if (spapr->vof) { + target_ulong stack_ptr = 0; + + spapr_setup_of_client(spapr, &stack_ptr); + spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT, + stack_ptr, spapr->initrd_base, + spapr->initrd_size); + } else { + spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT, + 0, fdt_addr, 0); + } + fdt = spapr_build_fdt(spapr, true, FDT_MAX_SIZE); - rc = fdt_pack(fdt); - - /* Should only fail if we've built a corrupted tree */ - assert(rc == 0); - - /* Load the fdt */ - qemu_fdt_dumpdtb(fdt, fdt_totalsize(fdt)); - cpu_physical_memory_write(fdt_addr, fdt, fdt_totalsize(fdt)); g_free(spapr->fdt_blob); spapr->fdt_size = fdt_totalsize(fdt); spapr->fdt_initial_size = spapr->fdt_size; spapr->fdt_blob = fdt; - /* Set up the entry state */ - spapr_cpu_set_entry_state(first_ppc_cpu, SPAPR_ENTRY_POINT, 0, fdt_addr, 0); - first_ppc_cpu->env.gpr[5] = 0; + if (spapr->vof) { + spapr_of_client_dt_finalize(spapr); + } else { + /* Load the fdt */ + rc = fdt_pack(spapr->fdt_blob); + /* Should only fail if we've built a corrupted tree */ + assert(rc == 0); + + spapr->fdt_size = fdt_totalsize(spapr->fdt_blob); + spapr->fdt_initial_size = spapr->fdt_size; + cpu_physical_memory_write(fdt_addr, spapr->fdt_blob, spapr->fdt_size); + } + + qemu_fdt_dumpdtb(spapr->fdt_blob, spapr->fdt_size); spapr->cas_reboot = false; @@ -3006,7 +3027,7 @@ static void spapr_machine_init(MachineState *machine) } if (bios_name == NULL) { - bios_name = FW_FILE_NAME; + bios_name = spapr->vof ? FW_FILE_NAME_VOF : FW_FILE_NAME; } filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); if (!filename) { @@ -3020,6 +3041,10 @@ static void spapr_machine_init(MachineState *machine) } g_free(filename); + if (spapr->vof) { + spapr_of_client_machine_init(spapr); + } + /* FIXME: Should register things through the MachineState's qdev * interface, this is a legacy from the sPAPREnvironment structure * which predated MachineState but had a similar function */ @@ -3249,6 +3274,20 @@ static void spapr_set_kernel_addr(Object *obj, Visitor *v, const char *name, visit_type_uint64(v, name, (uint64_t *)opaque, errp); } +static bool spapr_get_vof(Object *obj, Error **errp) +{ + SpaprMachineState *spapr = SPAPR_MACHINE(obj); + + return spapr->vof; +} + +static void spapr_set_vof(Object *obj, bool value, Error **errp) +{ + SpaprMachineState *spapr = SPAPR_MACHINE(obj); + + spapr->vof = value; +} + static char *spapr_get_ic_mode(Object *obj, Error **errp) { SpaprMachineState *spapr = SPAPR_MACHINE(obj); @@ -3362,6 +3401,11 @@ static void spapr_instance_init(Object *obj) " for -kernel is the default", NULL); spapr->kernel_addr = KERNEL_LOAD_ADDR; + object_property_add_bool(obj, "vof", spapr_get_vof, spapr_set_vof, NULL); + object_property_set_description(obj, "vof", "Enable Virtual Oepn Firmware", + NULL); + spapr->vof = false; + /* The machine class defines the default interrupt controller mode */ spapr->irq = smc->irq; object_property_add_str(obj, "ic-mode", spapr_get_ic_mode, diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c index 35de3ab95f42..63446db411f5 100644 --- a/hw/ppc/spapr_hcall.c +++ b/hw/ppc/spapr_hcall.c @@ -1844,12 +1844,16 @@ target_ulong do_client_architecture_support(PowerPCCPU *cpu, spapr_setup_hpt_and_vrma(spapr); } - fdt = spapr_build_fdt(spapr, false, fdt_bufsize); + fdt = spapr_build_fdt(spapr, spapr->vof, fdt_bufsize); g_free(spapr->fdt_blob); spapr->fdt_size = fdt_totalsize(fdt); spapr->fdt_initial_size = spapr->fdt_size; spapr->fdt_blob = fdt; + + if (spapr->vof) { + spapr_of_client_dt_finalize(spapr); + } } if (spapr->cas_reboot) { diff --git a/hw/ppc/spapr_of_client.c b/hw/ppc/spapr_of_client.c new file mode 100644 index 000000000000..4c476e138e60 --- /dev/null +++ b/hw/ppc/spapr_of_client.c @@ -0,0 +1,1221 @@ +#include "qemu/osdep.h" +#include "qemu-common.h" +#include +#include +#include "qapi/error.h" +#include "exec/memory.h" +#include "hw/ppc/spapr.h" +#include "hw/ppc/spapr_vio.h" +#include "hw/ppc/fdt.h" +#include "hw/block/block.h" +#include "sysemu/block-backend.h" +#include "sysemu/sysemu.h" +#include "chardev/char-fe.h" +#include "qom/qom-qobject.h" +#include "elf.h" +#include "hw/ppc/ppc.h" +#include "hw/loader.h" +#include "trace.h" + +/* + * OF 1275 "nextprop" description suggests is it 32 bytes max but + * LoPAPR defines "ibm,query-interrupt-source-number" which is 33 chars long. + */ +#define OF_PROPNAME_LEN_MAX 64 + +/* Copied from SLOF, and 4K is definitely not enough for GRUB */ +#define OF_STACK_SIZE 0x8000 + +/* 0..10000 is reserved for the VOF fw */ +#define OF_STACK_ADDR 0x10000 + +/* RTAS + room for FWNMI log */ +#define OF_RTAS_SIZE 2048 + +#define ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) + +typedef uint8_t SpaprOfNetBuffer[2048]; +typedef struct { + int head, tail, used; + SpaprOfNetBuffer b[32]; +} SpaprOfNetBuffers; + +typedef struct { + DeviceState *dev; + CharBackend *cbe; + char *params; + char *path; /* the path used to open the instance */ + uint32_t phandle; +} SpaprOfInstance; + +/* Defined as Big Endian */ +struct prom_args { + uint32_t service; + uint32_t nargs; + uint32_t nret; + uint32_t args[10]; +}; + +static void readstr(hwaddr pa, char *buf, int size) +{ + cpu_physical_memory_read(pa, buf, size); + if (buf[size - 1] != '\0') { + buf[size - 1] = '\0'; + if (strlen(buf) == size - 1) { + trace_spapr_of_client_error_str_truncated(buf, size); + } + } +} + +static bool cmpservice(const char *s, size_t len, + unsigned nargs, unsigned nret, + const char *s1, size_t len1, + unsigned nargscheck, unsigned nretcheck) +{ + if (strcmp(s, s1)) { + return false; + } + if ((nargscheck && (nargs != nargscheck)) || + (nretcheck && (nret != nretcheck))) { + trace_spapr_of_client_error_param(s, nargscheck, nretcheck, nargs, + nret); + return false; + } + + return true; +} + +static void split_path(const char *fullpath, char **node, char **unit, + char **part) +{ + const char *c, *p = NULL, *u = NULL; + + *node = *unit = *part = NULL; + + if (fullpath[0] == '\0') { + *node = g_strdup(fullpath); + return; + } + + for (c = fullpath + strlen(fullpath) - 1; c > fullpath; --c) { + if (*c == '/') { + break; + } + if (*c == ':') { + p = c + 1; + continue; + } + if (*c == '@') { + u = c + 1; + continue; + } + } + + if (p && u && p < u) { + p = NULL; + } + + if (u && p) { + *node = g_strndup(fullpath, u - fullpath - 1); + *unit = g_strndup(u, p - u - 1); + *part = g_strdup(p); + } else if (!u && p) { + *node = g_strndup(fullpath, p - fullpath - 1); + *part = g_strdup(p); + } else if (!p && u) { + *node = g_strndup(fullpath, u - fullpath - 1); + *unit = g_strdup(u); + } else { + *node = g_strdup(fullpath); + } +} + +static void prop_format(char *tval, int tlen, const void *prop, int len) +{ + int i; + const char *c; + char *t; + const char bin[] = "..."; + + for (i = 0, c = prop; i < len; ++i, ++c) { + if (*c == '\0' && i == len - 1) { + strncpy(tval, prop, tlen - 1); + return; + } + if (*c < 0x20 || *c >= 0x80) { + /* Not reliably printable string so assume it is binary */ + break; + } + } + + /* Accidentally the binary will look like big endian (which it is) */ + for (i = 0, c = prop, t = tval; i < len; ++i, ++c) { + if (t >= tval + tlen - sizeof(bin) - 1 - 2 - 1) { + strcpy(t, bin); + return; + } + if (i && i % 4 == 0 && i != len - 1) { + strcat(t, " "); + ++t; + } + t += sprintf(t, "%02X", *c & 0xFF); + } +} + +static int of_client_fdt_path_offset(const void *fdt, const char *node, + const char *unit) +{ + int offset; + + offset = fdt_path_offset(fdt, node); + + if (offset < 0 && unit) { + char *tmp = g_strdup_printf("%s@%s", node, unit); + + offset = fdt_path_offset(fdt, tmp); + g_free(tmp); + } + + return offset; +} + +static uint32_t of_client_finddevice(const void *fdt, uint32_t nodeaddr) +{ + char *node, *unit, *part; + char fullnode[1024]; + uint32_t ret = -1; + int offset; + + readstr(nodeaddr, fullnode, sizeof(fullnode)); + + split_path(fullnode, &node, &unit, &part); + offset = of_client_fdt_path_offset(fdt, node, unit); + if (offset >= 0) { + ret = fdt_get_phandle(fdt, offset); + } + trace_spapr_of_client_finddevice(fullnode, ret); + g_free(node); + g_free(unit); + g_free(part); + return (uint32_t) ret; +} + +static uint32_t of_client_canon(const void *fdt, uint32_t devaddr, + uint32_t bufaddr, uint32_t buflen) +{ + char dev[1024]; + uint32_t ret; + + /* + * OF1275: "This service converts the possibly ambiguous device-specifier to + * a fully qualified pathname, storing, atmost, buflen bytes as + * a null-terminated string in the memory buffer starting at the address + * buf." + * + * For now only only GRUB wanted this for "/vdevice/l-lan@71000001" which + * is pretty full qualified, just return this. + */ + readstr(devaddr, dev, sizeof(dev)); + ret = MIN(buflen, strlen(dev)); + cpu_physical_memory_write(bufaddr, dev, ret); + trace_spapr_of_client_canon(dev); + + return ret; +} + +static uint32_t of_client_getprop(const void *fdt, uint32_t nodeph, + uint32_t pname, uint32_t valaddr, + uint32_t vallen) +{ + char propname[OF_PROPNAME_LEN_MAX + 1]; + uint32_t ret = 0; + int proplen = 0; + const void *prop; + char trval[64] = ""; + int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph); + + readstr(pname, propname, sizeof(propname)); + if (strcmp(propname, "name") == 0) { + prop = fdt_get_name(fdt, nodeoff, &proplen); + proplen += 1; + } else { + prop = fdt_getprop(fdt, nodeoff, propname, &proplen); + } + + if (prop) { + int cb = MIN(proplen, vallen); + + cpu_physical_memory_write(valaddr, prop, cb); + /* + * OF1275 says: + * "Size is either the actual size of the property, or –1 if name + * does not exist", hence returning proplen instead of cb. + */ + ret = proplen; + prop_format(trval, sizeof(trval), prop, ret); + } else { + ret = -1; + } + trace_spapr_of_client_getprop(nodeph, propname, ret, trval); + + return ret; +} + +static uint32_t of_client_getproplen(const void *fdt, uint32_t nodeph, + uint32_t pname) +{ + char propname[OF_PROPNAME_LEN_MAX + 1]; + uint32_t ret = 0; + int proplen = 0; + const void *prop; + int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph); + + readstr(pname, propname, sizeof(propname)); + if (strcmp(propname, "name") == 0) { + prop = fdt_get_name(fdt, nodeoff, &proplen); + proplen += 1; + } else { + prop = fdt_getprop(fdt, nodeoff, propname, &proplen); + } + + if (prop) { + ret = proplen; + } else { + ret = -1; + } + trace_spapr_of_client_getproplen(nodeph, propname, ret); + + return ret; +} + +static uint32_t of_client_setprop(SpaprMachineState *spapr, + uint32_t nodeph, uint32_t pname, + uint32_t valaddr, uint32_t vallen) +{ + char propname[OF_PROPNAME_LEN_MAX + 1]; + uint32_t ret = -1; + int offset; + char trval[64] = ""; + + readstr(pname, propname, sizeof(propname)); + /* + * We only allow changing properties which we know how to update on + * the QEMU side. + */ + if (vallen == sizeof(uint32_t)) { + uint32_t val32 = ldl_be_phys(first_cpu->as, valaddr); + + if ((strcmp(propname, "linux,rtas-base") == 0) || + (strcmp(propname, "linux,rtas-entry") == 0)) { + spapr->rtas_base = val32; + } else if (strcmp(propname, "linux,initrd-start") == 0) { + spapr->initrd_base = val32; + } else if (strcmp(propname, "linux,initrd-end") == 0) { + spapr->initrd_size = val32 - spapr->initrd_base; + } else { + goto trace_exit; + } + } else if (vallen == sizeof(uint64_t)) { + uint64_t val64 = ldq_be_phys(first_cpu->as, valaddr); + + if (strcmp(propname, "linux,initrd-start") == 0) { + spapr->initrd_base = val64; + } else if (strcmp(propname, "linux,initrd-end") == 0) { + spapr->initrd_size = val64 - spapr->initrd_base; + } else { + goto trace_exit; + } + } else if (strcmp(propname, "bootargs") == 0) { + char val[1024]; + + readstr(valaddr, val, sizeof(val)); + g_free(spapr->bootargs); + spapr->bootargs = g_strdup(val); + } else { + goto trace_exit; + } + + offset = fdt_node_offset_by_phandle(spapr->fdt_blob, nodeph); + if (offset >= 0) { + uint8_t data[vallen]; + + cpu_physical_memory_read(valaddr, data, vallen); + if (!fdt_setprop(spapr->fdt_blob, offset, propname, data, vallen)) { + ret = vallen; + prop_format(trval, sizeof(trval), data, ret); + } + } + +trace_exit: + trace_spapr_of_client_setprop(nodeph, propname, trval, ret); + + return ret; +} + +static uint32_t of_client_nextprop(const void *fdt, uint32_t phandle, + uint32_t prevaddr, uint32_t nameaddr) +{ + int offset = fdt_node_offset_by_phandle(fdt, phandle); + char prev[OF_PROPNAME_LEN_MAX + 1]; + const char *tmp; + + readstr(prevaddr, prev, sizeof(prev)); + for (offset = fdt_first_property_offset(fdt, offset); + offset >= 0; + offset = fdt_next_property_offset(fdt, offset)) { + + if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) { + return 0; + } + if (prev[0] == '\0' || strcmp(prev, tmp) == 0) { + if (prev[0] != '\0') { + offset = fdt_next_property_offset(fdt, offset); + if (offset < 0) { + return 0; + } + } + if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) { + return 0; + } + + cpu_physical_memory_write(nameaddr, tmp, strlen(tmp) + 1); + return 1; + } + } + + return 0; +} + +static uint32_t of_client_peer(const void *fdt, uint32_t phandle) +{ + int ret; + + if (phandle == 0) { + ret = fdt_path_offset(fdt, "/"); + } else { + ret = fdt_next_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle)); + } + + if (ret < 0) { + ret = 0; + } else { + ret = fdt_get_phandle(fdt, ret); + } + + return ret; +} + +static uint32_t of_client_child(const void *fdt, uint32_t phandle) +{ + int ret = fdt_first_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle)); + + if (ret < 0) { + ret = 0; + } else { + ret = fdt_get_phandle(fdt, ret); + } + + return ret; +} + +static uint32_t of_client_parent(const void *fdt, uint32_t phandle) +{ + int ret = fdt_parent_offset(fdt, fdt_node_offset_by_phandle(fdt, phandle)); + + if (ret < 0) { + ret = 0; + } else { + ret = fdt_get_phandle(fdt, ret); + } + + return ret; +} + +static DeviceState *of_client_find_qom_dev(BusState *bus, const char *path) +{ + BusChild *kid; + + QTAILQ_FOREACH(kid, &bus->children, sibling) { + const char *p = qdev_get_fw_dev_path(kid->child); + BusState *child; + + if (p && strcmp(path, p) == 0) { + return kid->child; + } + QLIST_FOREACH(child, &kid->child->child_bus, sibling) { + DeviceState *d = of_client_find_qom_dev(child, path); + + if (d) { + return d; + } + } + } + return NULL; +} + +static uint32_t spapr_of_client_open(SpaprMachineState *spapr, const char *path) +{ + int offset; + uint32_t ret = 0; + SpaprOfInstance *inst = NULL; + char *node, *unit, *part; + + if (spapr->of_instance_last == 0xFFFFFFFF) { + /* We do not recycle ihandles yet */ + goto trace_exit; + } + + split_path(path, &node, &unit, &part); + + offset = of_client_fdt_path_offset(spapr->fdt_blob, node, unit); + if (offset < 0) { + trace_spapr_of_client_error_unknown_path(path); + goto trace_exit; + } + + inst = g_new0(SpaprOfInstance, 1); + inst->phandle = fdt_get_phandle(spapr->fdt_blob, offset); + g_assert(inst->phandle); + ++spapr->of_instance_last; + + inst->dev = of_client_find_qom_dev(sysbus_get_default(), node); + if (!inst->dev) { + char *tmp = g_strdup_printf("%s@%s", node, unit); + inst->dev = of_client_find_qom_dev(sysbus_get_default(), tmp); + g_free(tmp); + } + inst->path = g_strdup(path); + inst->params = part; + g_hash_table_insert(spapr->of_instances, + GINT_TO_POINTER(spapr->of_instance_last), + inst); + ret = spapr->of_instance_last; + + if (inst->dev) { + const char *cdevstr = object_property_get_str(OBJECT(inst->dev), + "chardev", NULL); + + if (cdevstr) { + Chardev *cdev = qemu_chr_find(cdevstr); + + if (cdev) { + inst->cbe = cdev->be; + } + } + } + +trace_exit: + trace_spapr_of_client_open(path, inst ? inst->phandle : 0, ret); + g_free(node); + g_free(unit); + + return ret; +} + +static uint32_t of_client_open(SpaprMachineState *spapr, uint32_t pathaddr) +{ + char path[256]; + + readstr(pathaddr, path, sizeof(path)); + + return spapr_of_client_open(spapr, path); +} + +static void of_client_close(SpaprMachineState *spapr, uint32_t ihandle) +{ + if (!g_hash_table_remove(spapr->of_instances, GINT_TO_POINTER(ihandle))) { + trace_spapr_of_client_error_unknown_ihandle_close(ihandle); + } +} + +static uint32_t of_client_instance_to_package(SpaprMachineState *spapr, + uint32_t ihandle) +{ + gpointer instp = g_hash_table_lookup(spapr->of_instances, + GINT_TO_POINTER(ihandle)); + uint32_t ret = -1; + + if (instp) { + ret = ((SpaprOfInstance *)instp)->phandle; + } + trace_spapr_of_client_instance_to_package(ihandle, ret); + + return ret; +} + +static uint32_t of_client_package_to_path(const void *fdt, uint32_t phandle, + uint32_t buf, uint32_t len) +{ + uint32_t ret = -1; + char tmp[256] = ""; + + if (0 == fdt_get_path(fdt, fdt_node_offset_by_phandle(fdt, phandle), tmp, + sizeof(tmp))) { + tmp[sizeof(tmp) - 1] = 0; + ret = MIN(len, strlen(tmp) + 1); + cpu_physical_memory_write(buf, tmp, ret); + } + + trace_spapr_of_client_package_to_path(phandle, tmp, ret); + + return ret; +} + +static uint32_t of_client_instance_to_path(SpaprMachineState *spapr, + uint32_t ihandle, uint32_t buf, + uint32_t len) +{ + uint32_t ret = -1; + uint32_t phandle = of_client_instance_to_package(spapr, ihandle); + char tmp[256] = ""; + + if (phandle != -1) { + if (0 == fdt_get_path(spapr->fdt_blob, + fdt_node_offset_by_phandle(spapr->fdt_blob, + phandle), + tmp, sizeof(tmp))) { + tmp[sizeof(tmp) - 1] = 0; + ret = MIN(len, strlen(tmp) + 1); + cpu_physical_memory_write(buf, tmp, ret); + } + } + trace_spapr_of_client_instance_to_path(ihandle, phandle, tmp, ret); + + return ret; +} + +static uint32_t of_client_write(SpaprMachineState *spapr, uint32_t ihandle, + uint32_t buf, uint32_t len) +{ + char tmp[1025]; + int toread, toprint, cb = MIN(len, 1024); + SpaprOfInstance *inst = (SpaprOfInstance *) + g_hash_table_lookup(spapr->of_instances, GINT_TO_POINTER(ihandle)); + + while (cb > 0) { + toread = MIN(cb, sizeof(tmp) - 1); + + cpu_physical_memory_read(buf, tmp, toread); + + toprint = toread; + if (inst) { + if (inst->cbe) { + toprint = qemu_chr_fe_write_all(inst->cbe, (uint8_t *) tmp, + toprint); + } + } else { + /* We normally open stdout so this is fallback */ + tmp[toprint] = '\0'; + printf("DBG[%d]%s", ihandle, tmp); + } + buf += toprint; + cb -= toprint; + } + + return len; +} + +static uint32_t of_client_read(SpaprMachineState *spapr, uint32_t ihandle, + uint32_t bufaddr, uint32_t len) +{ + uint32_t ret = 0; + SpaprOfInstance *inst = (SpaprOfInstance *) + g_hash_table_lookup(spapr->of_instances, GINT_TO_POINTER(ihandle)); + + if (inst) { + hwaddr xlat = 0; + hwaddr xlen = len; + MemoryRegion *mr = address_space_translate(&address_space_memory, + bufaddr, &xlat, &xlen, true, + MEMTXATTRS_UNSPECIFIED); + + if (mr && xlen == len) { + uint8_t *buf = memory_region_get_ram_ptr(mr) + xlat; + + if (inst->cbe) { + SpaprVioDevice *sdev = VIO_SPAPR_DEVICE(inst->dev); + + ret = vty_getchars(sdev, buf, len); /* qemu_chr_fe_read_all? */ + } + } + } + + return ret; +} + +static void of_client_clamed_dump(GArray *claimed) +{ +#ifdef DEBUG + int i; + SpaprOfClaimed c; + + for (i = 0; i < claimed->len; ++i) { + c = g_array_index(claimed, SpaprOfClaimed, i); + error_printf("CLAIMED %lx..%lx size=%ld\n", c.start, c.start + c.size, + c.size); + } +#endif +} + +static bool of_client_claim_avail(GArray *claimed, uint64_t virt, uint64_t size) +{ + int i; + SpaprOfClaimed c; + + for (i = 0; i < claimed->len; ++i) { + c = g_array_index(claimed, SpaprOfClaimed, i); + if ((c.start <= virt && virt < c.start + c.size) || + (virt <= c.start && c.start < virt + size)) { + return false; + } + } + + return true; +} + +static void of_client_claim_add(GArray *claimed, uint64_t virt, uint64_t size) +{ + SpaprOfClaimed newclaim; + + newclaim.start = virt; + newclaim.size = size; + g_array_append_val(claimed, newclaim); +} + +/* + * "claim" claims memory at @virt if @align==0; otherwise it allocates + * memory at the requested alignment. + */ +static void of_client_dt_memory_available(void *fdt, GArray *claimed, + uint64_t base); + +static uint64_t of_client_claim(SpaprMachineState *spapr, uint64_t virt, + uint64_t size, uint64_t align) +{ + uint64_t ret; + + if (size == 0) { + ret = -1; + } else if (align == 0) { + if (!of_client_claim_avail(spapr->claimed, virt, size)) { + ret = -1; + } else { + ret = virt; + } + } else { + spapr->claimed_base = ALIGN(spapr->claimed_base, align); + while (1) { + if (spapr->claimed_base >= spapr->rma_size) { + error_report("Out of RMA memory for the OF client"); + return -1; + } + if (of_client_claim_avail(spapr->claimed, spapr->claimed_base, + size)) { + break; + } + spapr->claimed_base += size; + } + ret = spapr->claimed_base; + } + + if (ret != -1) { + spapr->claimed_base = MAX(spapr->claimed_base, ret + size); + of_client_claim_add(spapr->claimed, ret, size); + /* The client reads "/memory@0/available" to know where it can claim */ + of_client_dt_memory_available(spapr->fdt_blob, spapr->claimed, + spapr->claimed_base); + } + trace_spapr_of_client_claim(virt, size, align, ret); + + return ret; +} + +static uint32_t of_client_release(SpaprMachineState *spapr, uint64_t virt, + uint64_t size) +{ + uint32_t ret = -1; + int i; + GArray *claimed = spapr->claimed; + SpaprOfClaimed c; + + for (i = 0; i < claimed->len; ++i) { + c = g_array_index(claimed, SpaprOfClaimed, i); + if (c.start == virt && c.size == size) { + g_array_remove_index(claimed, i); + ret = 0; + break; + } + } + + trace_spapr_of_client_release(virt, size, ret); + + return ret; +} + +static void of_client_instantiate_rtas(SpaprMachineState *spapr, uint32_t base) +{ + error_report("The firmware should have instantiated RTAS"); + exit(1); +} + +static uint32_t of_client_call_method(SpaprMachineState *spapr, + uint32_t methodaddr, uint32_t ihandle, + uint32_t param1, uint32_t param2, + uint32_t param3, uint32_t param4, + uint32_t *ret2) +{ + uint32_t ret = -1; + char method[256] = ""; + SpaprOfInstance *inst = NULL; + + if (!ihandle) { + goto trace_exit; + } + + inst = (SpaprOfInstance *) g_hash_table_lookup(spapr->of_instances, + GINT_TO_POINTER(ihandle)); + if (!inst) { + goto trace_exit; + } + + readstr(methodaddr, method, sizeof(method)); + + if (strcmp(inst->path, "/") == 0) { + if (strcmp(method, "ibm,client-architecture-support") == 0) { + ret = do_client_architecture_support(POWERPC_CPU(first_cpu), spapr, + param1, FDT_MAX_SIZE); + *ret2 = 0; + } + } else if (strcmp(inst->path, "/rtas") == 0) { + if (strcmp(method, "instantiate-rtas") == 0) { + of_client_instantiate_rtas(spapr, param1); + ret = 0; + *ret2 = param1; /* rtasbase */ + } + } else if (inst->cbe) { + if (strcmp(method, "color!") == 0) { + /* do not bother about colors now */ + ret = 0; + } + } else { + trace_spapr_of_client_error_unknown_method(method); + } + +trace_exit: + trace_spapr_of_client_method(ihandle, method, param1, ret, *ret2); + + return ret; +} + +static uint32_t of_client_call_interpret(SpaprMachineState *spapr, + uint32_t cmdaddr, uint32_t param1, + uint32_t param2, uint32_t *ret2) +{ + uint32_t ret = -1; + char cmd[256] = ""; + + readstr(cmdaddr, cmd, sizeof(cmd)); + trace_spapr_of_client_interpret(cmd, param1, param2, ret, *ret2); + + return ret; +} + +static void of_client_quiesce(SpaprMachineState *spapr) +{ + /* We could as well just free the blob as there is no use for it from now */ + int rc = fdt_pack(spapr->fdt_blob); + /* Should only fail if we've built a corrupted tree */ + assert(rc == 0); + + spapr->fdt_size = fdt_totalsize(spapr->fdt_blob); + spapr->fdt_initial_size = spapr->fdt_size; + of_client_clamed_dump(spapr->claimed); +} + +static target_ulong spapr_h_of_client(PowerPCCPU *cpu, SpaprMachineState *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong of_client_args = ppc64_phys_to_real(args[0]); + struct prom_args pargs = { 0 }; + char service[64]; + unsigned nargs, nret; + int i, servicelen; + + cpu_physical_memory_read(of_client_args, &pargs, sizeof(pargs)); + nargs = be32_to_cpu(pargs.nargs); + nret = be32_to_cpu(pargs.nret); + readstr(be32_to_cpu(pargs.service), service, sizeof(service)); + servicelen = strlen(service); + + if (nargs >= ARRAY_SIZE(pargs.args)) { + /* Bounds checking: something is just very wrong */ + return H_PARAMETER; + } + +#define cmpserv(s, a, r) \ + cmpservice(service, servicelen, nargs, nret, (s), sizeof(s), (a), (r)) + + if (cmpserv("finddevice", 1, 1)) { + pargs.args[nargs] = + of_client_finddevice(spapr->fdt_blob, + be32_to_cpu(pargs.args[0])); + } else if (cmpserv("canon", 3, 1)) { + pargs.args[nargs] = + of_client_canon(spapr->fdt_blob, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2])); + } else if (cmpserv("getprop", 4, 1)) { + pargs.args[nargs] = + of_client_getprop(spapr->fdt_blob, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2]), + be32_to_cpu(pargs.args[3])); + } else if (cmpserv("getproplen", 2, 1)) { + pargs.args[nargs] = + of_client_getproplen(spapr->fdt_blob, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1])); + } else if (cmpserv("setprop", 4, 1)) { + pargs.args[nargs] = + of_client_setprop(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2]), + be32_to_cpu(pargs.args[3])); + } else if (cmpserv("nextprop", 3, 1)) { + pargs.args[nargs] = + of_client_nextprop(spapr->fdt_blob, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2])); + } else if (cmpserv("peer", 1, 1)) { + pargs.args[nargs] = + of_client_peer(spapr->fdt_blob, + be32_to_cpu(pargs.args[0])); + } else if (cmpserv("child", 1, 1)) { + pargs.args[nargs] = + of_client_child(spapr->fdt_blob, + be32_to_cpu(pargs.args[0])); + } else if (cmpserv("parent", 1, 1)) { + pargs.args[nargs] = + of_client_parent(spapr->fdt_blob, + be32_to_cpu(pargs.args[0])); + } else if (cmpserv("open", 1, 1)) { + pargs.args[nargs] = of_client_open(spapr, be32_to_cpu(pargs.args[0])); + } else if (cmpserv("close", 1, 0)) { + of_client_close(spapr, be32_to_cpu(pargs.args[0])); + } else if (cmpserv("instance-to-package", 1, 1)) { + pargs.args[nargs] = + of_client_instance_to_package(spapr, + be32_to_cpu(pargs.args[0])); + } else if (cmpserv("package-to-path", 3, 1)) { + pargs.args[nargs] = + of_client_package_to_path(spapr->fdt_blob, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2])); + } else if (cmpserv("instance-to-path", 3, 1)) { + pargs.args[nargs] = + of_client_instance_to_path(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2])); + } else if (cmpserv("write", 3, 1)) { + pargs.args[nargs] = + of_client_write(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2])); + } else if (cmpserv("read", 3, 1)) { + pargs.args[nargs] = + of_client_read(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2])); + } else if (cmpserv("claim", 3, 1)) { + pargs.args[nargs] = + of_client_claim(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2])); + } else if (cmpserv("release", 2, 0)) { + pargs.args[nargs] = + of_client_release(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1])); + } else if (cmpserv("call-method", 0, 0)) { + pargs.args[nargs] = + of_client_call_method(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2]), + be32_to_cpu(pargs.args[3]), + be32_to_cpu(pargs.args[4]), + be32_to_cpu(pargs.args[5]), + &pargs.args[nargs + 1]); + } else if (cmpserv("interpret", 0, 0)) { + pargs.args[nargs] = + of_client_call_interpret(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2]), + &pargs.args[nargs + 1]); + } else if (cmpserv("milliseconds", 0, 1)) { + pargs.args[nargs] = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); + } else if (cmpserv("quiesce", 0, 0)) { + of_client_quiesce(spapr); + } else if (cmpserv("exit", 0, 0)) { + error_report("Stopped as the VM requested \"exit\""); + vm_stop(RUN_STATE_PAUSED); /* Or qemu_system_guest_panicked(NULL); ? */ + } else { + trace_spapr_of_client_error_unknown_service(service, nargs, nret); + pargs.args[nargs] = -1; + } + + for (i = 0; i < nret; ++i) { + pargs.args[nargs + i] = be32_to_cpu(pargs.args[nargs + i]); + } + + /* Copy what is needed as GRUB allocates only required minimum on stack */ + cpu_physical_memory_write(of_client_args, &pargs, + sizeof(uint32_t) * (3 + nargs + nret)); + + return H_SUCCESS; +} + +static void of_instance_free(gpointer data) +{ + SpaprOfInstance *inst = (SpaprOfInstance *) data; + + g_free(inst->params); + g_free(inst->path); + g_free(inst); +} + +void spapr_setup_of_client(SpaprMachineState *spapr, target_ulong *stack_ptr) +{ + if (spapr->claimed) { + g_array_unref(spapr->claimed); + } + if (spapr->of_instances) { + g_hash_table_unref(spapr->of_instances); + } + + spapr->claimed = g_array_new(false, false, sizeof(SpaprOfClaimed)); + spapr->of_instances = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, of_instance_free); + + *stack_ptr = of_client_claim(spapr, OF_STACK_ADDR, OF_STACK_SIZE, + OF_STACK_SIZE); + if (*stack_ptr == -1) { + error_report("Memory allocation for stack failed"); + exit(1); + } + /* + * Stack grows downwards and we also reserve here space for + * the minimum stack frame. + */ + *stack_ptr += OF_STACK_SIZE - 0x20; + + if (spapr->kernel_size && + of_client_claim(spapr, spapr->kernel_addr, + spapr->kernel_size, 0) == -1) { + error_report("Memory for kernel is in use"); + exit(1); + } + + if (spapr->initrd_size && + of_client_claim(spapr, spapr->initrd_base, + spapr->initrd_size, 0) == -1) { + error_report("Memory for initramdisk is in use"); + exit(1); + } + + /* + * We skip writing FDT as nothing expects it; OF client interface is + * going to be used for reading the device tree. + */ +} + +static gint of_claimed_compare_func(gconstpointer a, gconstpointer b) +{ + return ((SpaprOfClaimed *)a)->start - ((SpaprOfClaimed *)b)->start; +} + +static void of_client_dt_memory_available(void *fdt, GArray *claimed, + uint64_t base) +{ + int i, n, offset, proplen = 0; + uint64_t *mem0_reg; + struct { uint64_t start, size; } *avail; + + if (!fdt || !claimed) { + return; + } + + offset = fdt_path_offset(fdt, "/memory@0"); + _FDT(offset); + + mem0_reg = (uint64_t *) fdt_getprop(fdt, offset, "reg", &proplen); + if (!mem0_reg || proplen != 2 * sizeof(uint64_t)) { + return; + } + + g_array_sort(claimed, of_claimed_compare_func); + of_client_clamed_dump(claimed); + + avail = g_malloc0(sizeof(uint64_t) * 2 * claimed->len); + for (i = 0, n = 0; i < claimed->len; ++i) { + SpaprOfClaimed c = g_array_index(claimed, SpaprOfClaimed, i); + + avail[n].start = c.start + c.size; + if (i < claimed->len - 1) { + SpaprOfClaimed cn = g_array_index(claimed, SpaprOfClaimed, i + 1); + + avail[n].size = cn.start - avail[n].start; + } else { + avail[n].size = be64_to_cpu(mem0_reg[1]) - avail[n].start; + } + + if (avail[n].size) { +#ifdef DEBUG + error_printf("AVAIL %lx..%lx size=%ld\n", avail[n].start, + avail[n].start + avail[n].size, avail[n].size); +#endif + avail[n].start = cpu_to_be64(avail[n].start); + avail[n].size = cpu_to_be64(avail[n].size); + ++n; + } + } + _FDT((fdt_setprop(fdt, offset, "available", avail, + sizeof(uint64_t) * 2 * n))); + g_free(avail); +} + +void spapr_of_client_dt(SpaprMachineState *spapr, void *fdt) +{ + uint32_t phandle; + int i, offset, proplen = 0; + const void *prop; + bool found = false; + GArray *phandles = g_array_new(false, false, sizeof(uint32_t)); + const char *nodename; + char *stdout_path = spapr_vio_stdout_path(spapr->vio_bus); + int aliases; + aliases = fdt_add_subnode(fdt, 0, "aliases"); + + if (stdout_path) { + fdt_setprop_string(fdt, aliases, "hvterm", stdout_path); + } + + /* Add options now, doing it at the end of this __func__ breaks it :-/ */ + offset = fdt_add_subnode(fdt, 0, "options"); + if (offset > 0) { + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) != -1) { + _FDT(fdt_setprop_cell(fdt, offset, "screen-#columns", ws.ws_col)); + _FDT(fdt_setprop_cell(fdt, offset, "screen-#rows", ws.ws_row)); + } + _FDT(fdt_setprop_cell(fdt, offset, "real-mode?", 1)); + } + + /* Add "disk" nodes to SCSI hosts */ + for (offset = fdt_next_node(fdt, -1, NULL), phandle = 1; + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL), ++phandle) { + + nodename = fdt_get_name(fdt, offset, NULL); + if (strncmp(nodename, "scsi@", 5) == 0 || + strncmp(nodename, "v-scsi@", 7) == 0) { + int disk_node_off = fdt_add_subnode(fdt, offset, "disk"); + + fdt_setprop_string(fdt, disk_node_off, "device_type", "block"); + } + } + + /* Find all predefined phandles */ + for (offset = fdt_next_node(fdt, -1, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + prop = fdt_getprop(fdt, offset, "phandle", &proplen); + if (prop && proplen == sizeof(uint32_t)) { + phandle = fdt32_ld(prop); + g_array_append_val(phandles, phandle); + } + } + + /* Assign phandles skipping the predefined ones */ + for (offset = fdt_next_node(fdt, -1, NULL), phandle = 1; + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL), ++phandle) { + + prop = fdt_getprop(fdt, offset, "phandle", &proplen); + if (prop) { + continue; + } + /* Check if the current phandle is not allocated already */ + for ( ; ; ++phandle) { + for (i = 0, found = false; i < phandles->len; ++i) { + if (phandle == g_array_index(phandles, uint32_t, i)) { + found = true; + break; + } + } + if (!found) { + break; + } + } + _FDT(fdt_setprop_cell(fdt, offset, "phandle", phandle)); + } + g_array_unref(phandles); + + of_client_dt_memory_available(fdt, spapr->claimed, spapr->claimed_base); + + /* Advertise RTAS presense, the FW instantiates the RTAS blob */ + offset = fdt_path_offset(fdt, "/rtas"); + _FDT(offset); + _FDT(fdt_setprop_cell(fdt, offset, "rtas-size", OF_RTAS_SIZE)); +} + +void spapr_of_client_dt_finalize(SpaprMachineState *spapr) +{ + void *fdt = spapr->fdt_blob; + char *stdout_path = spapr_vio_stdout_path(spapr->vio_bus); + int chosen = fdt_path_offset(fdt, "/chosen"); + size_t cb = 0; + char *bootlist = get_boot_devices_list(&cb); + + /* + * SLOF-less setup requires an open instance of stdout for early + * kernel printk. By now all phandles are settled so we can open + * the default serial console. + */ + if (stdout_path) { + _FDT(fdt_setprop_cell(fdt, chosen, "stdout", + spapr_of_client_open(spapr, stdout_path))); + _FDT(fdt_setprop_cell(fdt, chosen, "stdin", + spapr_of_client_open(spapr, stdout_path))); + } + + if (bootlist) { + _FDT(fdt_setprop_string(fdt, chosen, "bootpath", bootlist)); + _FDT(fdt_setprop_string(fdt, chosen, "bootargs", + spapr->bootargs ? spapr->bootargs : "")); + } + + /* + _FDT(fdt_setprop_cell(fdt, chosen, "cpu", + spapr_of_client_open(spapr, + "/cpus/PowerPC,POWER8@0"))); + _FDT(fdt_setprop_cell(fdt, chosen, "memory", memph)); + _FDT(fdt_setprop_cell(fdt, chosen, "mmu", memph)); + */ +} + +void spapr_of_client_machine_init(SpaprMachineState *spapr) +{ + spapr_register_hypercall(KVMPPC_H_OF_CLIENT, spapr_h_of_client); +} diff --git a/pc-bios/vof/bootmem.c b/pc-bios/vof/bootmem.c new file mode 100644 index 000000000000..de7d5fc76431 --- /dev/null +++ b/pc-bios/vof/bootmem.c @@ -0,0 +1,13 @@ +#include "vof.h" + +void boot_from_memory(uint64_t initrd, uint64_t initrdsize) +{ + uint64_t kern[2]; + phandle chosen = ci_finddevice("/chosen"); + + if (ci_getprop(chosen, "qemu,boot-kernel", kern, sizeof(kern)) != + sizeof(kern)) + return; + + do_boot(kern[0], initrd, initrdsize); +} diff --git a/pc-bios/vof/ci.c b/pc-bios/vof/ci.c new file mode 100644 index 000000000000..143676421984 --- /dev/null +++ b/pc-bios/vof/ci.c @@ -0,0 +1,136 @@ +#include "vof.h" + +struct prom_args { + uint32_t service; + uint32_t nargs; + uint32_t nret; + uint32_t args[10]; +}; + +#define ADDR(x) (uint32_t)(x) + +extern uint32_t ci_entry(uint32_t params); + +extern unsigned long hv_rtas(unsigned long params); +extern unsigned int hv_rtas_size; + +bool prom_handle(struct prom_args *pargs) +{ + void *rtasbase; + uint32_t rtassize = 0; + phandle rtas; + + if (strcmp("call-method", (void *)(unsigned long) pargs->service)) + return false; + + if (strcmp("instantiate-rtas", (void *)(unsigned long) pargs->args[0])) + return false; + + rtas = ci_finddevice("/rtas"); + ci_getprop(rtas, "rtas-size", &rtassize, sizeof(rtassize)); + if (rtassize < hv_rtas_size) + return false; + + rtasbase = (void *)(unsigned long) pargs->args[2]; + + memcpy(rtasbase, hv_rtas, hv_rtas_size); + pargs->args[pargs->nargs] = 0; + pargs->args[pargs->nargs + 1] = pargs->args[2]; + + return true; +} + +void prom_entry(uint32_t args) +{ + if (!prom_handle((void *)(unsigned long) args)) + ci_entry(args); +} + +int call_prom(const char *service, int nargs, int nret, ...) +{ + int i; + struct prom_args args; + va_list list; + + args.service = ADDR(service); + args.nargs = nargs; + args.nret = nret; + + va_start(list, nret); + for (i = 0; i < nargs; i++) + args.args[i] = va_arg(list, prom_arg_t); + va_end(list); + + for (i = 0; i < nret; i++) + args.args[nargs+i] = 0; + + if (ci_entry((uint32_t)(&args)) < 0) + return PROM_ERROR; + + return (nret > 0) ? args.args[nargs] : 0; +} + +void ci_panic(const char *str) +{ + ci_stdout(str); + call_prom("exit", 0, 0); +} + +phandle ci_finddevice(const char *path) +{ + return call_prom("finddevice", 1, 1, path); +} + +uint32_t ci_getprop(phandle ph, const char *propname, void *prop, int len) +{ + return call_prom("getprop", 4, 1, ph, propname, prop, len); +} + +ihandle ci_open(const char *path) +{ + return call_prom("open", 1, 1, path); +} + +void ci_close(ihandle ih) +{ + call_prom("close", 1, 0, ih); +} + +uint32_t ci_read(ihandle ih, void *buf, int len) +{ + return call_prom("read", 3, 1, ih, buf, len); +} + +uint32_t ci_write(ihandle ih, const void *buf, int len) +{ + return call_prom("write", 3, 1, ih, buf, len); +} + +void ci_stdoutn(const char *buf, int len) +{ + static ihandle istdout; + + if (!istdout) { + phandle chosen = ci_finddevice("/chosen"); + + ci_getprop(chosen, "stdout", &istdout, sizeof(istdout)); + } + ci_write(istdout, buf, len); +} + +void ci_stdout(const char *buf) +{ + ci_stdoutn(buf, strlen(buf)); +} + +void *ci_claim(void *virt, uint32_t size, uint32_t align) +{ + uint32_t ret = call_prom("claim", 3, 1, ADDR(virt), size, align); + + return (void *) (unsigned long) ret; +} + +uint32_t ci_release(void *virt, uint32_t size) +{ + return call_prom("release", 2, 1, ADDR(virt), size); +} diff --git a/pc-bios/vof/libc.c b/pc-bios/vof/libc.c new file mode 100644 index 000000000000..8603aedcb32c --- /dev/null +++ b/pc-bios/vof/libc.c @@ -0,0 +1,91 @@ +#include "vof.h" + +int strlen(const char *s) +{ + int len = 0; + + while (*s != 0) { + len += 1; + s += 1; + } + + return len; +} + +int strcmp(const char *s1, const char *s2) +{ + while (*s1 != 0 && *s2 != 0) { + if (*s1 != *s2) + break; + s1 += 1; + s2 += 1; + } + + return *s1 - *s2; +} + +void *memcpy(void *dest, const void *src, size_t n) +{ + char *cdest; + const char *csrc = src; + + cdest = dest; + while (n-- > 0) { + *cdest++ = *csrc++; + } + + return dest; +} + +int memcmp(const void *ptr1, const void *ptr2, size_t n) +{ + const unsigned char *p1 = ptr1; + const unsigned char *p2 = ptr2; + + while (n-- > 0) { + if (*p1 != *p2) + return (*p1 - *p2); + p1 += 1; + p2 += 1; + } + + return 0; +} + +void *memmove(void *dest, const void *src, size_t n) +{ + char *cdest; + const char *csrc; + int i; + + /* Do the buffers overlap in a bad way? */ + if (src < dest && src + n >= dest) { + /* Copy from end to start */ + cdest = dest + n - 1; + csrc = src + n - 1; + for (i = 0; i < n; i++) { + *cdest-- = *csrc--; + } + } + else { + /* Normal copy is possible */ + cdest = dest; + csrc = src; + for (i = 0; i < n; i++) { + *cdest++ = *csrc++; + } + } + + return dest; +} + +void *memset(void *dest, int c, size_t size) +{ + unsigned char *d = (unsigned char *)dest; + + while (size-- > 0) { + *d++ = (unsigned char)c; + } + + return dest; +} diff --git a/pc-bios/vof/main.c b/pc-bios/vof/main.c new file mode 100644 index 000000000000..eb9aa8b78a8f --- /dev/null +++ b/pc-bios/vof/main.c @@ -0,0 +1,23 @@ +#include "vof.h" + + +void do_boot(unsigned long addr, unsigned long _r3, unsigned long _r4) +{ + register unsigned long r3 __asm__("r3") = _r3; + register unsigned long r4 __asm__("r4") = _r4; + register unsigned long r5 __asm__("r5") = (unsigned long) _prom_entry; + + ((client *)(uint32_t)addr)(); +} + +void entry_c(void) +{ + register unsigned long r3 __asm__("r3"); + register unsigned long r4 __asm__("r4"); + register unsigned long r5 __asm__("r5"); + uint64_t initrd = r3, initrdsize = r4; + + ci_stdout("*** Virtual Open Firmware ***\n"); + boot_from_memory(initrd, initrdsize); + ci_panic("*** No boot target ***\n"); +} diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events index 9ea620f23c85..640d0649394d 100644 --- a/hw/ppc/trace-events +++ b/hw/ppc/trace-events @@ -21,6 +21,27 @@ spapr_update_dt(unsigned cb) "New blob %u bytes" spapr_update_dt_failed_size(unsigned cbold, unsigned cbnew, unsigned magic) "Old blob %u bytes, new blob %u bytes, magic 0x%x" spapr_update_dt_failed_check(unsigned cbold, unsigned cbnew, unsigned magic) "Old blob %u bytes, new blob %u bytes, magic 0x%x" +# spapr_client.c +spapr_of_client_error_str_truncated(const char *s, int len) "%s truncated to %d" +spapr_of_client_error_param(const char *method, int nargscheck, int nretcheck, int nargs, int nret) "%s takes/returns %d/%d, not %d/%d" +spapr_of_client_error_unknown_service(const char *service, int nargs, int nret) "\"%s\" args=%d rets=%d" +spapr_of_client_error_unknown_method(const char *method) "\"%s\"" +spapr_of_client_error_unknown_ihandle_close(uint32_t ih) "ih=0x%x" +spapr_of_client_error_unknown_path(const char *path) "\"%s\"" +spapr_of_client_finddevice(const char *path, uint32_t ph) "\"%s\" => ph=0x%x" +spapr_of_client_canon(const char *path) "\"%s\"" +spapr_of_client_claim(uint32_t virt, uint32_t size, uint32_t align, uint32_t ret) "virt=0x%x size=0x%x align=0x%x => 0x%x" +spapr_of_client_release(uint32_t virt, uint32_t size, uint32_t ret) "virt=0x%x size=0x%x => 0x%x" +spapr_of_client_method(uint32_t ihandle, const char *method, uint32_t param, uint32_t ret, uint32_t ret2) "ih=0x%x \"%s\"(0x%x) => 0x%x 0x%x" +spapr_of_client_getprop(uint32_t ph, const char *prop, uint32_t ret, const char *val) "ph=0x%x \"%s\" => len=%d [%s]" +spapr_of_client_getproplen(uint32_t ph, const char *prop, uint32_t ret) "ph=0x%x \"%s\" => len=%d" +spapr_of_client_setprop(uint32_t ph, const char *prop, const char *val, uint32_t ret) "ph=0x%x \"%s\" [%s] => len=%d" +spapr_of_client_open(const char *path, uint32_t ph, uint32_t ih) "%s ph=0x%x => ih=0x%x" +spapr_of_client_interpret(const char *cmd, uint32_t param1, uint32_t param2, uint32_t ret, uint32_t ret2) "[%s] 0x%x 0x%x => 0x%x 0x%x" +spapr_of_client_package_to_path(uint32_t ph, const char *tmp, uint32_t ret) "ph=0x%x => %s len=%d" +spapr_of_client_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, uint32_t ret) "ih=0x%x ph=0x%x => %s len=%d" +spapr_of_client_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x" + # spapr_hcall_tpm.c spapr_h_tpm_comm(const char *device_path, uint64_t operation) "tpm_device_path=%s operation=0x%"PRIu64 spapr_tpm_execute(uint64_t data_in, uint64_t data_in_sz, uint64_t data_out, uint64_t data_out_sz) "data_in=0x%"PRIx64", data_in_sz=%"PRIu64", data_out=0x%"PRIx64", data_out_sz=%"PRIu64 diff --git a/pc-bios/README b/pc-bios/README index 193aa375fc63..ea10610c708e 100644 --- a/pc-bios/README +++ b/pc-bios/README @@ -16,6 +16,8 @@ https://github.com/aik/SLOF, and the image currently in qemu is built from git tag qemu-slof-20200217. +- vof is a minimalistic firmware to work with -machine pseries,vof=on. + - sgabios (the Serial Graphics Adapter option ROM) provides a means for legacy x86 software to communicate with an attached serial console as if a video card were attached. The master sources reside in a subversion diff --git a/pc-bios/vof.bin b/pc-bios/vof.bin new file mode 100755 index 0000000000000000000000000000000000000000..152d86a094ce376ee8962c3f26afeccc9e7cae58 GIT binary patch literal 4272 zcmd^BUuauZ82{2_?#>0g4;@4n=&h^L)RtAqL!)I$XD4YhW@T0?7Mj>xZ7^+Wnz~F9 zbF+V}d(f0kaIm#|kg^wHLLYo`6GRH`p@KpNW9`6)!Cr<4c656EednI*(tlapd^2!3 zC+B?k{J!t^{X0be?_Y44XxJnwEIZ8gCiyW`q`gEaGAf+@*?$T9KjRLuiSyGGp49Y(r$rgj8y(1M@Ty6}H)y%eM&M2${UEuP<-usBNO-HR#v@9Xq9t9{Ab|Uu(F_%YMM@VIF|d zR2+g|S_Cgbr@#)Z8(>Q>(Rs@rEzo0{(?g`OZe176MgCsmhyEqLz_s-NejnBwcmp4- ztHM04x7+4=3r^ST`X$G}W9d31eBu#)yg()Bqk$`LzNNPwv)J#tNSrTPz-OAg$7eFW z>^&jRoZGPu{o5_h!Kvj7C9nhQwVbQcJ{7VmV?Yn;x?nEg`$7C)!{f%gtNL@xyZPS7 z;!5fR-K;$80sav341D$XKDBR)I4VB^uZCK7Mybei=E+m!m}!b+Q8y@JK6ry`kBHkD z%LmYrbDZVeQqBcA-zIYNR_$@>oacccsU6qI#Wm8HG290B!sPr~_-CJ?GOmmj;cMVz zBo4=f8}(Jl$nUwvxq;g+Wn8$&)9+JX<`{zS-GLBlWsk^z*44%Fz%s>NsxLN3*=9?Y zAPz6dRg{|rph|Dwm+PnXM3TS!uLhYzmD46pq>f4Bj){4u1%Ch7w7pPg*ZPjcfp5c>~~}4^RUbtu7mL@$h|m4 z+mX{;6T|$UYhk~F_-&w&?HkDQ{oR2cazVfDqU9rn&jStM%<_pUefO~T4+%~OWgefa z!1e==b6R2Fi+dm|<3O#5gY|bsU9-MN{sfLO8e~9k_z9#W^Rp38i{+;x1VrP?u^WduroN9K~ zz~_nU6*%lO=CfJi9H_x(HE?-igfVO`H{s9;%vK9?^`{Eks&QBY?(usy;I>QLxBo+2 zeiMrK7V5p9bIp3sQ4{R!KrTGXdT}2yzBT;lBb}T#*iYEd8`wd|)_eg X-Patchwork-Id: 1241232 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=ozlabs.ru Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 48NPWp1KQZz9sRm for ; Thu, 20 Feb 2020 17:17:26 +1100 (AEDT) Received: from localhost ([::1]:36666 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4f9I-0008Jf-2h for incoming@patchwork.ozlabs.org; Thu, 20 Feb 2020 01:17:24 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:56328) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4f8c-0007wP-26 for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:16:45 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j4f8Y-00058A-IC for qemu-devel@nongnu.org; Thu, 20 Feb 2020 01:16:41 -0500 Received: from [107.174.27.60] (port=52534 helo=ozlabs.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1j4f8Y-00057Q-61; Thu, 20 Feb 2020 01:16:38 -0500 Received: from fstn1-p1.ozlabs.ibm.com (localhost [IPv6:::1]) by ozlabs.ru (Postfix) with ESMTP id 42C2DAE80017; Thu, 20 Feb 2020 01:15:04 -0500 (EST) From: Alexey Kardashevskiy To: qemu-devel@nongnu.org Subject: [PATCH qemu v7 5/5] spapr/vof: Add basic support for MBR/GPT/GRUB Date: Thu, 20 Feb 2020 17:16:22 +1100 Message-Id: <20200220061622.15064-6-aik@ozlabs.ru> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200220061622.15064-1-aik@ozlabs.ru> References: <20200220061622.15064-1-aik@ozlabs.ru> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-Received-From: 107.174.27.60 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Alexey Kardashevskiy , Paolo Bonzini , qemu-ppc@nongnu.org, David Gibson Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" This hooks up disks to block backends so vof.bin can read MBR/GPT, find a bootloader and run it. This bypasses the device drivers and goes straight to the backend. This implements basic support for 32bit big endian bootloader; tested on GRUB. Signed-off-by: Alexey Kardashevskiy --- pc-bios/vof/Makefile | 2 +- pc-bios/vof/vof.h | 15 +++ hw/ppc/spapr_of_client.c | 64 +++++++++ pc-bios/vof/bootblock.c | 242 ++++++++++++++++++++++++++++++++++ pc-bios/vof/ci.c | 11 ++ pc-bios/vof/elf32.c | 273 +++++++++++++++++++++++++++++++++++++++ pc-bios/vof/main.c | 1 + hw/ppc/trace-events | 4 + pc-bios/vof.bin | Bin 4272 -> 9180 bytes 9 files changed, 611 insertions(+), 1 deletion(-) create mode 100644 pc-bios/vof/bootblock.c create mode 100644 pc-bios/vof/elf32.c diff --git a/pc-bios/vof.bin b/pc-bios/vof.bin index 152d86a094ce376ee8962c3f26afeccc9e7cae58..2c6629d2c72d5d1657c5e6791c15dabbc7aecb48 100755 GIT binary patch literal 9180 zcmd^FeQZ+P(RiQq*me&MiZ^7n98JVVpArulBoRyMmvED1royB@7#A^ zVuEo!+W!{m>D_bhdG~yO-0KkifB%(?5^dCp+D9#QMSh~1<-er>(Q$KpIY;lA7^6+? zr?U;>S{loec&wSyg9UWO^md*x`mM-^3o*8pw&f!ygUyeT9re+=J zwoqYWh;kES7{m1C>Gmvl?_2>!%(l8<+>r^+&$c+oszAAu8fS5`(19<>xa4uVH7 zcr21UI$&=n?5$xhC))yV2kQYeT=8nyCQs-^@Z_6|@gm5wifET1kLIh;^`2_dn77j1 z`Y?Yl>HGgj`hu>>2k2K~e1qPg59U>6J;t#NYqhY6L)fvOy1|bI zT{)w9tL%7-`O@9QF{An1y3TXFCjHBt6Zoui)8D~=fuVEg)Ubu>n~U-FI=4yrY{0cK z4e&vw{rWL{Uj_I&J>Ix?T6^AfZ{GKobS3!#Z$_L|fPOXN4EhSKnq7PoKN|T8dKu)i z-L|ASXFWOM>@%)l1bKsk`qjrd{|LX`W7q&Za*Q*cRpKeYbEm+2*T_BD^&IEE60&&* zN#Q&h+Y__KFc+ZgL&84AF&opBz9MW5I<1lp`-BxaR^Z6*InP-^HzaWs?BfWzZ4a{# z!S+_4ANjIE#6R<@us<+Pd==T_izIHYfy;RC0gJR1g#CoPgTfIHQlGEpr-T1oM2Era1{q6Lc(WhW z;LSBAdshkhIX>_%g$+kAUxqiY2V~rE9<1vE-u+!P2Qke#F~BhA!jKC8T||DOS106wq@=6VTrLSZ{$0g{TA{8_&pkV2#Boljl>tN$e*5Wp+R?i;G3iS>M_VG`b5+zFXsrO4<#4m zEJB^CWqok{iye)5QUClL@0dSi;-i0eqHh=dLr~WjNdHM^hYWo5)dHq#(7KQ#{1&b? z(oU6WbVLTf7Nuv14)=`FkzNZ8_U6zBy`^-lx0a6g#%Z{BfKK!d(MazYolID0G?7D} zCQ9jaqLw~O#OYjOfG$AZ%aHf#jq)bEkgFT=_CemAkhdQ^_xaped@i-~88~(Zd1fvt zsOc(Vz=~ePk6xs#2mJtWR>K!m_&)2x9@M`OaX;N#>Uw~(SPyo5Tj@iu7S%$mBZk)k zc1qX@{k8jdi#`J5EW#K)qxjw{`vO~lEEn3`^CQtEIQOwHo_R6C=M3-KjZNJa~T)#ED6t(@Ei%xXBhNm z8}zQ*%PQA6&MA~PSp0b2^1C0;TPbLP54Hn;YrV<3UFNhDea=L;k;|0U-XSH>JEp8o zSd_{{jJmfBro@=iaAU6eHF|0VxmiJORtnh0hyg2nn2?hx zhP9yn0 zlu{6TU-5`DvDj;T3-5*R%)1LdC!J%xa31lwEk_YsHgQg=R+3_C^6y&qV^8m&iF(hl4=5hQh9c|}c<4^HS$E@OtJ`#*P;wM@kL7a;CR0_=al=&9=G3u1GyLnHjg7<9yZe8fp zh%;rY8E3Z;M;Y{e?qy^6TQLwi)z9 zuUJ7x^afHt;=Wd#MUkI+P|J8;!Wv)~e1UE1NU8q_5U1!#9jc)9 z9BjQEyxK)99gV5xT7cB|um{I}s_%T5w+a6GsSLdm?r$zG179Az9|CN{J0Cc95vMWO zP1KsxVbF8bkT`eSe+@B-bRw}2N1<{gqgpBcEnhi^}YjraL->|@@2n{?RU z&AqD3bzi%hMt}MZH4A&F1CTwG_hE4;w`@st)rv@HmBKk+Jye0;Szsf+)5@g&@i!@I zgNoX5EBiR()yAEVkz-QwkU`vT-^%($k1Ou`(c`+%!{&llr^uDiJLhY*x0pkHleL2V z-742OhRmGJcT)^IA*(ol^IB9{5s9wge#H2#XgbE>LyW&@YJYKlF!9l5^oYHT`YP!0 znO@HEJqy#ZiQhL6_k53JoFmTsQm)yh-xLAPm1A+AZm!Gya!r~hkJCPr_NhDJk3oH6K*G6ly)qTp1kLu_#ThJvm*DnO~P`0d{V;lu6g`(2}>P~ z|4YJBC*yySu++&o*KXcl>SX*Kz_;ont7WcE>mTwCt$Mdd98c(N^ze) zeJ|xev&fDglp~!2z#ZmzGb zTB_(x&B=JmTey9I)8J0b7Or2?@_+8 zLaQu&Vl9vNd^g|w;Tj;4b&n-~o+$VtBR_SunP2z_<@jKb|-69K>@N&mf*-c!r4h($ delta 501 zcmYk$F-Tis5C`!4PBHJr^u0$>P?1)AdaF=B|`=~6j}(V(4j+zP&%Z9l3q|thaVhw-+lM5&6b8s4~2Ul zOn_=M2Jm8f9~#50gXRsSGT-sYgsv3|i6rh-C@)^)-V!P3>z$D2V8|=zkr_~A5nLv? ztg?yjPTW<>sjdZq+b4AD@Q78qcFf?mS@tXQLVTt*^^LZbv)gOTgpRk=P!qT{NQ=%S zZt{|I9XG=i?M`B@N{8K3RCM=J$)#Z~MHeoQ8l3w$o{hJfpQ!Hi{Tpu7c{nPb|KFV4 zSmQ%|b`b`xNg1{R08e%RVg>NZbs34b{`9wlL@uCHp2@H*$Y?th_g}W((~k!UEqi^R zHQxI5l@_&Un0-$lG@lx~&GGw&YT5!?4GQT)>eIXC3Z*$*M)wR@RsO)w8G`mXgzPH_ z^KIb#kLgq&z~(#pqL1Ov04au-{3Cg)jLa@Ug1pqT&w diff --git a/pc-bios/vof/Makefile b/pc-bios/vof/Makefile index 49f7e240eeff..7e4227bb2cc6 100644 --- a/pc-bios/vof/Makefile +++ b/pc-bios/vof/Makefile @@ -8,7 +8,7 @@ build-all: vof.bin %.o: %.c cc -m32 -mbig-endian -c -fno-stack-protector -Wno-builtin-declaration-mismatch -o $@ $< -vof.elf: entry.o main.o libc.o ci.o bootmem.o +vof.elf: entry.o main.o libc.o ci.o bootmem.o bootblock.o elf32.o ld -nostdlib -e_start -Tl.lds -EB -o $@ $^ %.bin: %.elf diff --git a/pc-bios/vof/vof.h b/pc-bios/vof/vof.h index 738b2539aa19..b16270b289b7 100644 --- a/pc-bios/vof/vof.h +++ b/pc-bios/vof/vof.h @@ -37,6 +37,8 @@ phandle ci_finddevice(const char *path); uint32_t ci_getprop(phandle ph, const char *propname, void *prop, int len); ihandle ci_open(const char *path); void ci_close(ihandle ih); +uint32_t ci_block_size(ihandle ih); +uint32_t ci_seek(ihandle ih, uint64_t offset); uint32_t ci_read(ihandle ih, void *buf, int len); uint32_t ci_write(ihandle ih, const void *buf, int len); void ci_stdout(const char *buf); @@ -44,5 +46,18 @@ void ci_stdoutn(const char *buf, int len); void *ci_claim(void *virt, uint32_t size, uint32_t align); uint32_t ci_release(void *virt, uint32_t size); +/* ELF */ +int elf_load_file(void *file_addr, uint32_t *entry, + int (*pre_load)(void*, long), + void (*post_load)(void*, long)); + +/* booting from blockdev */ +void boot_block(void); + /* booting from -kernel */ void boot_from_memory(uint64_t initrd, uint64_t initrdsize); + +/* bswap */ +#define le16_to_cpu(x) __builtin_bswap16(x) +#define le32_to_cpu(x) __builtin_bswap32(x) +#define le64_to_cpu(x) __builtin_bswap64(x) diff --git a/hw/ppc/spapr_of_client.c b/hw/ppc/spapr_of_client.c index 4c476e138e60..a36b32487349 100644 --- a/hw/ppc/spapr_of_client.c +++ b/hw/ppc/spapr_of_client.c @@ -43,6 +43,9 @@ typedef struct { typedef struct { DeviceState *dev; CharBackend *cbe; + BlockBackend *blk; + uint64_t blk_pos; + uint16_t blk_physical_block_size; char *params; char *path; /* the path used to open the instance */ uint32_t phandle; @@ -494,6 +497,8 @@ static uint32_t spapr_of_client_open(SpaprMachineState *spapr, const char *path) if (inst->dev) { const char *cdevstr = object_property_get_str(OBJECT(inst->dev), "chardev", NULL); + const char *blkstr = object_property_get_str(OBJECT(inst->dev), + "drive", NULL); if (cdevstr) { Chardev *cdev = qemu_chr_find(cdevstr); @@ -501,6 +506,13 @@ static uint32_t spapr_of_client_open(SpaprMachineState *spapr, const char *path) if (cdev) { inst->cbe = cdev->be; } + } else if (blkstr) { + BlockConf conf = { 0 }; + + inst->blk = blk_by_name(blkstr); + conf.blk = inst->blk; + blkconf_blocksizes(&conf); + inst->blk_physical_block_size = conf.physical_block_size; } } @@ -602,6 +614,8 @@ static uint32_t of_client_write(SpaprMachineState *spapr, uint32_t ihandle, if (inst->cbe) { toprint = qemu_chr_fe_write_all(inst->cbe, (uint8_t *) tmp, toprint); + } else if (inst->blk) { + trace_spapr_of_client_blk_write(ihandle, len); } } else { /* We normally open stdout so this is fallback */ @@ -636,6 +650,17 @@ static uint32_t of_client_read(SpaprMachineState *spapr, uint32_t ihandle, SpaprVioDevice *sdev = VIO_SPAPR_DEVICE(inst->dev); ret = vty_getchars(sdev, buf, len); /* qemu_chr_fe_read_all? */ + } else if (inst->blk) { + int rc = blk_pread(inst->blk, inst->blk_pos, buf, len); + + if (rc > 0) { + ret = rc; + } + trace_spapr_of_client_blk_read(ihandle, inst->blk_pos, len, + ret); + if (rc > 0) { + inst->blk_pos += rc; + } } } } @@ -643,6 +668,25 @@ static uint32_t of_client_read(SpaprMachineState *spapr, uint32_t ihandle, return ret; } +static uint32_t of_client_seek(SpaprMachineState *spapr, uint32_t ihandle, + uint32_t hi, uint32_t lo) +{ + uint32_t ret = -1; + uint64_t pos = ((uint64_t) hi << 32) | lo; + SpaprOfInstance *inst = (SpaprOfInstance *) + g_hash_table_lookup(spapr->of_instances, GINT_TO_POINTER(ihandle)); + + if (inst) { + if (inst->blk) { + inst->blk_pos = pos; + ret = 1; + trace_spapr_of_client_blk_seek(ihandle, pos, ret); + } + } + + return ret; +} + static void of_client_clamed_dump(GArray *claimed) { #ifdef DEBUG @@ -797,6 +841,20 @@ static uint32_t of_client_call_method(SpaprMachineState *spapr, /* do not bother about colors now */ ret = 0; } + } else if (inst->blk) { + if (strcmp(method, "block-size") == 0) { + ret = 0; + *ret2 = inst->blk_physical_block_size; + } else if (strcmp(method, "#blocks") == 0) { + ret = 0; + *ret2 = blk_getlength(inst->blk) / inst->blk_physical_block_size; + } + } else if (inst->dev) { + if (strcmp(method, "vscsi-report-luns") == 0) { + /* TODO: Not implemented yet, not clear when it is really needed */ + ret = -1; + *ret2 = 1; + } } else { trace_spapr_of_client_error_unknown_method(method); } @@ -934,6 +992,12 @@ static target_ulong spapr_h_of_client(PowerPCCPU *cpu, SpaprMachineState *spapr, be32_to_cpu(pargs.args[0]), be32_to_cpu(pargs.args[1]), be32_to_cpu(pargs.args[2])); + } else if (cmpserv("seek", 3, 1)) { + pargs.args[nargs] = + of_client_seek(spapr, + be32_to_cpu(pargs.args[0]), + be32_to_cpu(pargs.args[1]), + be32_to_cpu(pargs.args[2])); } else if (cmpserv("claim", 3, 1)) { pargs.args[nargs] = of_client_claim(spapr, diff --git a/pc-bios/vof/bootblock.c b/pc-bios/vof/bootblock.c new file mode 100644 index 000000000000..cbf585746734 --- /dev/null +++ b/pc-bios/vof/bootblock.c @@ -0,0 +1,242 @@ +#include "vof.h" + +#define UUID_FMT_LEN 36 +#define UUID_FMT "%02hhx%02hhx%02hhx%02hhx-" \ + "%02hhx%02hhx-%02hhx%02hhx-" \ + "%02hhx%02hhx-" \ + "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" + +typedef struct { + union { + unsigned char data[16]; + struct { + /* Generated in BE endian, can be swapped with qemu_uuid_bswap. */ + uint32_t time_low; + uint16_t time_mid; + uint16_t time_high_and_version; + uint8_t clock_seq_and_reserved; + uint8_t clock_seq_low; + uint8_t node[6]; + } fields; + }; +} UUID; + +struct gpt_header { + char signature[8]; + char revision[4]; + uint32_t header_size; + uint32_t crc; + uint32_t reserved; + uint64_t current_lba; + uint64_t backup_lba; + uint64_t first_usable_lba; + uint64_t last_usable_lba; + char guid[16]; + uint64_t partition_entries_lba; + uint32_t nr_partition_entries; + uint32_t size_partition_entry; + uint32_t crc_partitions; +}; + +#define GPT_SIGNATURE "EFI PART" +#define GPT_REVISION "\0\0\1\0" /* revision 1.0 */ + +struct gpt_entry { + char partition_type_guid[16]; + char unique_guid[16]; + uint64_t first_lba; + uint64_t last_lba; + uint64_t attributes; + char name[72]; /* UTF-16LE */ +}; + +#define GPT_MIN_PARTITIONS 128 +#define GPT_PT_ENTRY_SIZE 128 +#define SECTOR_SIZE 512 + +static int find_prep_partition_on_gpt(ihandle blk, uint8_t *lba01, + uint64_t *offset, uint64_t *size) +{ + unsigned long i, partnum, partentrysize; + int ret; + struct gpt_header *hdr = (struct gpt_header *) (lba01 + SECTOR_SIZE); + UUID prep_uuid = { .fields = + { 0x9e1a2d38, 0xc612, 0x4316, 0xaa, 0x26, + { 0x8b, 0x49, 0x52, 0x1e, 0x5a, 0x8b} } }; + + if (memcmp(hdr, "EFI PART", 8)) + return -1; + + partnum = le32_to_cpu(hdr->nr_partition_entries); + partentrysize = le32_to_cpu(hdr->size_partition_entry); + + if (partentrysize < 128 || partentrysize > 512) { + return -1; + } + + for (i = 0; i < partnum; ++i) { + uint8_t partdata[partentrysize]; + struct gpt_entry *entry = (struct gpt_entry *) partdata; + uint64_t first, last; + UUID parttype; + char uuid[UUID_FMT_LEN + 1]; + + ci_seek(blk, 2 * SECTOR_SIZE + i * partentrysize); + ret = ci_read(blk, partdata, sizeof(partdata)); + if (ret < 0) + return ret; + else if (!ret) + return -1; + + memcpy(parttype.data, entry->partition_type_guid, 16); + first = le64_to_cpu(entry->first_lba); + last = le64_to_cpu(entry->last_lba); + + if (!memcmp(&parttype, &prep_uuid, sizeof(parttype))) { + *offset = first * SECTOR_SIZE; + *size = (last - first) * SECTOR_SIZE; + } + } + + if (*offset) + return 0; + + return -1; +} + +struct partition_record { + uint8_t bootable; + uint8_t start_head; + uint32_t start_cylinder; + uint8_t start_sector; + uint8_t system; + uint8_t end_head; + uint8_t end_cylinder; + uint8_t end_sector; + uint32_t start_sector_abs; + uint32_t nb_sectors_abs; +}; + +static void read_partition(uint8_t *p, struct partition_record *r) +{ + r->bootable = p[0]; + r->start_head = p[1]; + r->start_cylinder = p[3] | ((p[2] << 2) & 0x0300); + r->start_sector = p[2] & 0x3f; + r->system = p[4]; + r->end_head = p[5]; + r->end_cylinder = p[7] | ((p[6] << 2) & 0x300); + r->end_sector = p[6] & 0x3f; + r->start_sector_abs = le32_to_cpu(*(uint32_t *)(p + 8)); + r->nb_sectors_abs = le32_to_cpu(*(uint32_t *)(p + 12)); +} + +static int find_prep_partition(ihandle blk, uint64_t *offset, uint64_t *size) +{ + uint8_t lba01[SECTOR_SIZE * 2]; + int i; + int ret = -1; + + ci_seek(blk, 0); + ret = ci_read(blk, lba01, sizeof(lba01)); + if (ret < 0) + return ret; + + if (lba01[510] != 0x55 || lba01[511] != 0xaa) + return find_prep_partition_on_gpt(blk, lba01, offset, size); + + for (i = 0; i < 4; i++) { + struct partition_record part; + + read_partition(&lba01[446 + 16 * i], &part); + + if (!part.system || !part.nb_sectors_abs) { + continue; + } + + /* 0xEE == GPT */ + if (part.system == 0xEE) { + ret = find_prep_partition_on_gpt(blk, lba01, offset, size); + } + /* 0x41 == PReP */ + if (part.system == 0x41) { + *offset = part.start_sector_abs; + *offset <<= 9; + *size = (uint64_t)part.nb_sectors_abs << 9; + ret = 0; + } + } + + return ret; +} + +static int elf_pre_load(void *destaddr, long size) +{ + void *ret = ci_claim(destaddr, size, 0); + + return (ret == destaddr) ? 0 : -1; +} + +static void try_boot_block_device(ihandle blk, const char *path) +{ + uint32_t rc, elf_addr = 0; + uint64_t offset = 0, size = 0; + void *grub; + int ret; + + if (find_prep_partition(blk, &offset, &size)) + return; + + grub = ci_claim((void*)0x20000000, size, 0); + if (!grub) + return; + + ci_seek(blk, offset); + rc = ci_read(blk, grub, size); + if (rc <= 0) { + ci_release(grub, size); + return; + } + + ret = elf_load_file(grub, &elf_addr, elf_pre_load, NULL); + ci_release(grub, size); + if (ret < 0) + return; + + ci_stdout("** Booting from "); + ci_stdout(path); + ci_stdout("\n"); + do_boot(elf_addr, 0, 0); +} + +void boot_block(void) +{ + char bootlist[2048], *cur, *next; + uint32_t cb, blk; + phandle chosen = ci_finddevice("/chosen"); + + cb = ci_getprop(chosen, "qemu,boot-list", bootlist, + sizeof(bootlist) - 1); + bootlist[sizeof(bootlist) - 1] = '\0'; + + if (strlen(bootlist) == 0) + return; + + for (cur = bootlist; cb > 0; cur = next + 1) { + for (next = cur; cb > 0; --cb) { + if (*next == '\n') { + *next = '\0'; + ++next; + --cb; + break; + } + } + + blk = ci_open(cur); + if (!blk) + continue; + + try_boot_block_device(blk, cur); + ci_close(blk); + } +} diff --git a/pc-bios/vof/ci.c b/pc-bios/vof/ci.c index 143676421984..584776b2ff5b 100644 --- a/pc-bios/vof/ci.c +++ b/pc-bios/vof/ci.c @@ -96,6 +96,17 @@ void ci_close(ihandle ih) call_prom("close", 1, 0, ih); } +uint32_t ci_block_size(ihandle ih) +{ + return 512; +} + +uint32_t ci_seek(ihandle ih, uint64_t offset) +{ + return call_prom("seek", 3, 1, ih, (prom_arg_t)(offset >> 32), + (prom_arg_t)(offset & 0xFFFFFFFFUL)); +} + uint32_t ci_read(ihandle ih, void *buf, int len) { return call_prom("read", 3, 1, ih, buf, len); diff --git a/pc-bios/vof/elf32.c b/pc-bios/vof/elf32.c new file mode 100644 index 000000000000..8e0623557d3f --- /dev/null +++ b/pc-bios/vof/elf32.c @@ -0,0 +1,273 @@ +/****************************************************************************** + * Copyright (c) 2004, 2011 IBM Corporation + * All rights reserved. + * This program and the accompanying materials + * are made available under the terms of the BSD License + * which accompanies this distribution, and is available at + * http://www.opensource.org/licenses/bsd-license.php + * + * Contributors: + * IBM Corporation - initial implementation + *****************************************************************************/ + +/* + * 32-bit ELF loader + */ +#include "vof.h" + +/* ELF object file types */ +#define ET_NONE 0 /* No file type */ +#define ET_REL 1 /* Relocatable file */ +#define ET_EXEC 2 /* Executable file */ +#define ET_DYN 3 /* Shared object file */ +#define ET_CORE 4 /* Core file */ + +/* ELF object endian */ +#define ELFDATA2LSB 1 /* 2's complement, little endian */ +#define ELFDATA2MSB 2 /* 2's complement, big endian */ + +struct ehdr32 { + uint32_t ei_ident; + uint8_t ei_class; + uint8_t ei_data; + uint8_t ei_version; + uint8_t ei_pad[9]; + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint32_t e_entry; + uint32_t e_phoff; + uint32_t e_shoff; + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +}; + +struct phdr32 { + uint32_t p_type; + uint32_t p_offset; + uint32_t p_vaddr; + uint32_t p_paddr; + uint32_t p_filesz; + uint32_t p_memsz; + uint32_t p_flags; + uint32_t p_align; +}; + +static inline void bswap_16p (uint16_t *x) +{ + *x = __builtin_bswap16(*x); +} + +static inline void bswap_32p (uint32_t *x) +{ + *x = __builtin_bswap32(*x); +} + +static inline void bswap_64p (uint64_t *x) +{ + *x = __builtin_bswap64(*x); +} + +static struct phdr32* +get_phdr32(void *file_addr) +{ + return (struct phdr32 *) (((unsigned char *)file_addr) + + ((struct ehdr32 *)file_addr)->e_phoff); +} + +static void +load_segment(void *file_addr, struct phdr32 *phdr, signed long offset, + int (*pre_load)(void*, long), + void (*post_load)(void*, long)) +{ + unsigned long src = phdr->p_offset + (unsigned long) file_addr; + unsigned long destaddr; + + destaddr = (unsigned long)phdr->p_paddr; + destaddr = destaddr + offset; + + /* check if we're allowed to copy */ + if (pre_load != NULL) { + if (pre_load((void*)destaddr, phdr->p_memsz) != 0) + return; + } + + /* copy into storage */ + memmove((void *)destaddr, (void *)src, phdr->p_filesz); + + /* clear bss */ + memset((void *)(destaddr + phdr->p_filesz), 0, + phdr->p_memsz - phdr->p_filesz); + + if (phdr->p_memsz && post_load) { + post_load((void*)destaddr, phdr->p_memsz); + } +} + +unsigned int +elf_load_segments32(void *file_addr, signed long offset, + int (*pre_load)(void*, long), + void (*post_load)(void*, long)) +{ + struct ehdr32 *ehdr = (struct ehdr32 *) file_addr; + /* Calculate program header address */ + struct phdr32 *phdr = get_phdr32(file_addr); + int i; + + /* loop e_phnum times */ + for (i = 0; i <= ehdr->e_phnum; i++) { + /* PT_LOAD ? */ + if (phdr->p_type == 1) { + if (phdr->p_paddr != phdr->p_vaddr) { + return 0; + } + + /* copy segment */ + load_segment(file_addr, phdr, offset, pre_load, + post_load); + } + /* step to next header */ + phdr = (struct phdr32 *)(((uint8_t *)phdr) + ehdr->e_phentsize); + } + + /* Entry point is always a virtual address, so translate it + * to physical before returning it */ + return ehdr->e_entry; +} + +/** + * Return the base address for loading (i.e. the address of the first PT_LOAD + * segment) + * @param file_addr pointer to the ELF file in memory + * @return the base address + */ +long +elf_get_base_addr32(void *file_addr) +{ + struct ehdr32 *ehdr = (struct ehdr32 *) file_addr; + struct phdr32 *phdr = get_phdr32(file_addr); + int i; + + /* loop e_phnum times */ + for (i = 0; i <= ehdr->e_phnum; i++) { + /* PT_LOAD ? */ + if (phdr->p_type == 1) { + return phdr->p_paddr; + } + /* step to next header */ + phdr = (struct phdr32 *)(((uint8_t *)phdr) + ehdr->e_phentsize); + } + + return 0; +} + +uint32_t elf_get_eflags_32(void *file_addr) +{ + struct ehdr32 *ehdr = (struct ehdr32 *) file_addr; + + return ehdr->e_flags; +} + +void +elf_byteswap_header32(void *file_addr) +{ + struct ehdr32 *ehdr = (struct ehdr32 *) file_addr; + struct phdr32 *phdr; + int i; + + bswap_16p(&ehdr->e_type); + bswap_16p(&ehdr->e_machine); + bswap_32p(&ehdr->e_version); + bswap_32p(&ehdr->e_entry); + bswap_32p(&ehdr->e_phoff); + bswap_32p(&ehdr->e_shoff); + bswap_32p(&ehdr->e_flags); + bswap_16p(&ehdr->e_ehsize); + bswap_16p(&ehdr->e_phentsize); + bswap_16p(&ehdr->e_phnum); + bswap_16p(&ehdr->e_shentsize); + bswap_16p(&ehdr->e_shnum); + bswap_16p(&ehdr->e_shstrndx); + + phdr = get_phdr32(file_addr); + + /* loop e_phnum times */ + for (i = 0; i <= ehdr->e_phnum; i++) { + bswap_32p(&phdr->p_type); + bswap_32p(&phdr->p_offset); + bswap_32p(&phdr->p_vaddr); + bswap_32p(&phdr->p_paddr); + bswap_32p(&phdr->p_filesz); + bswap_32p(&phdr->p_memsz); + bswap_32p(&phdr->p_flags); + bswap_32p(&phdr->p_align); + + /* step to next header */ + phdr = (struct phdr32 *)(((uint8_t *)phdr) + ehdr->e_phentsize); + } +} + +/** + * elf_check_file tests if the file at file_addr is + * a correct endian, ELF PPC executable + * @param file_addr pointer to the start of the ELF file + * @return the class (1 for 32 bit, 2 for 64 bit) + * -1 if it is not an ELF file + * -2 if it has the wrong endianness + * -3 if it is not an ELF executable + * -4 if it is not for PPC + */ +static int +elf_check_file(unsigned long *file_addr) +{ + struct ehdr32 *ehdr = (struct ehdr32 *) file_addr; + + /* check if it is an ELF image at all */ + if (ehdr->ei_ident != 0x7f454c46) + return -1; + + if (ELFDATA2MSB != ehdr->ei_data) { + switch (ehdr->ei_class) { + case 1: + elf_byteswap_header32(file_addr); + break; + } + } + + /* check if it is an ELF executable ... and also + * allow DYN files, since this is specified by ePAPR */ + if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) + return -3; + + /* check if it is a PPC ELF executable */ + if (ehdr->e_machine != 0x14 && ehdr->e_machine != 0x15) + return -4; + + return ehdr->ei_class; +} + +int elf_load_file(void *file_addr, uint32_t *entry, + int (*pre_load)(void*, long), + void (*post_load)(void*, long)) +{ + int type = elf_check_file(file_addr); + struct ehdr32 *ehdr = (struct ehdr32 *) file_addr; + + switch (type) { + case 1: + *entry = elf_load_segments32(file_addr, 0, pre_load, post_load); + if (ehdr->ei_data != ELFDATA2MSB) { + type = 5; /* LE32 ABIv1 */ + } + break; + } + if (*entry == 0) + type = 0; + + return type; +} diff --git a/pc-bios/vof/main.c b/pc-bios/vof/main.c index eb9aa8b78a8f..d0935e78edd8 100644 --- a/pc-bios/vof/main.c +++ b/pc-bios/vof/main.c @@ -19,5 +19,6 @@ void entry_c(void) ci_stdout("*** Virtual Open Firmware ***\n"); boot_from_memory(initrd, initrdsize); + boot_block(); ci_panic("*** No boot target ***\n"); } diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events index 640d0649394d..9405d7fb2b84 100644 --- a/hw/ppc/trace-events +++ b/hw/ppc/trace-events @@ -38,6 +38,10 @@ spapr_of_client_getproplen(uint32_t ph, const char *prop, uint32_t ret) "ph=0x%x spapr_of_client_setprop(uint32_t ph, const char *prop, const char *val, uint32_t ret) "ph=0x%x \"%s\" [%s] => len=%d" spapr_of_client_open(const char *path, uint32_t ph, uint32_t ih) "%s ph=0x%x => ih=0x%x" spapr_of_client_interpret(const char *cmd, uint32_t param1, uint32_t param2, uint32_t ret, uint32_t ret2) "[%s] 0x%x 0x%x => 0x%x 0x%x" +spapr_of_client_blk_write(uint32_t ih, uint32_t len) "0x%x => len=%d" +spapr_of_client_blk_read(uint32_t ih, uint64_t pos, uint32_t len, uint32_t ret) "ih=0x%x @0x%"PRIx64" size=%d => %d" +spapr_of_client_blk_seek(uint32_t ih, uint64_t pos, uint32_t ret) "ih=0x%x 0x%"PRIx64" => %d" +spapr_of_client_blk_bootloader_read(uint64_t offset, uint64_t size) "0x%"PRIx64" size=0x%"PRIx64 spapr_of_client_package_to_path(uint32_t ph, const char *tmp, uint32_t ret) "ph=0x%x => %s len=%d" spapr_of_client_instance_to_path(uint32_t ih, uint32_t ph, const char *tmp, uint32_t ret) "ih=0x%x ph=0x%x => %s len=%d" spapr_of_client_instance_to_package(uint32_t ih, uint32_t ph) "ih=0x%x => ph=0x%x"