diff options
author | Paolo Bonzini <pbonzini@redhat.com> | 2013-02-05 12:20:00 +0100 |
---|---|---|
committer | Paolo Bonzini <pbonzini@redhat.com> | 2013-03-01 15:01:19 +0100 |
commit | 9f64bd8aec7c31c76fa0954aaee1475d482662b4 (patch) | |
tree | 85f63f0b1345f2097a5f653489d00f116af4c6dd /hw/ppc | |
parent | c68c4a56e93f54b374c5207f0185f8e9fa2aec3b (diff) |
ppc: move more files to hw/ppc
These sPAPR files do not implement devices, move them over.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'hw/ppc')
-rw-r--r-- | hw/ppc/Makefile.objs | 8 | ||||
-rw-r--r-- | hw/ppc/spapr_events.c | 321 | ||||
-rw-r--r-- | hw/ppc/spapr_hcall.c | 741 | ||||
-rw-r--r-- | hw/ppc/spapr_iommu.c | 293 | ||||
-rw-r--r-- | hw/ppc/spapr_rtas.c | 334 |
5 files changed, 1693 insertions, 4 deletions
diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs index acc9961ab0..4de02098e2 100644 --- a/hw/ppc/Makefile.objs +++ b/hw/ppc/Makefile.objs @@ -1,10 +1,9 @@ # PREP target obj-y += mc146818rtc.o # IBM pSeries (sPAPR) -obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_rtas.o obj-$(CONFIG_PSERIES) += spapr_vty.o spapr_llan.o spapr_vscsi.o -obj-$(CONFIG_PSERIES) += spapr_pci.o pci/pci-hotplug.o spapr_iommu.o -obj-$(CONFIG_PSERIES) += spapr_events.o spapr_nvram.o +obj-$(CONFIG_PSERIES) += spapr_pci.o pci/pci-hotplug.o +obj-$(CONFIG_PSERIES) += spapr_nvram.o # PowerPC 4xx boards obj-y += ppc4xx_pci.o # PowerPC OpenPIC @@ -18,7 +17,8 @@ obj-y := $(addprefix ../,$(obj-y)) # shared objects obj-y += ppc.o ppc_booke.o # IBM pSeries (sPAPR) -obj-$(CONFIG_PSERIES) += spapr.o xics.o spapr_vio.o +obj-$(CONFIG_PSERIES) += spapr.o xics.o spapr_vio.o spapr_events.o +obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_iommu.o spapr_rtas.o # PowerPC 4xx boards obj-y += ppc405_boards.o ppc4xx_devs.o ppc405_uc.o ppc440_bamboo.o # PReP diff --git a/hw/ppc/spapr_events.c b/hw/ppc/spapr_events.c new file mode 100644 index 0000000000..ce78f0922e --- /dev/null +++ b/hw/ppc/spapr_events.c @@ -0,0 +1,321 @@ +/* + * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator + * + * RTAS events handling + * + * Copyright (c) 2012 David Gibson, IBM Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "cpu.h" +#include "sysemu/sysemu.h" +#include "char/char.h" +#include "hw/qdev.h" +#include "sysemu/device_tree.h" + +#include "hw/spapr.h" +#include "hw/spapr_vio.h" + +#include <libfdt.h> + +struct rtas_error_log { + uint32_t summary; +#define RTAS_LOG_VERSION_MASK 0xff000000 +#define RTAS_LOG_VERSION_6 0x06000000 +#define RTAS_LOG_SEVERITY_MASK 0x00e00000 +#define RTAS_LOG_SEVERITY_ALREADY_REPORTED 0x00c00000 +#define RTAS_LOG_SEVERITY_FATAL 0x00a00000 +#define RTAS_LOG_SEVERITY_ERROR 0x00800000 +#define RTAS_LOG_SEVERITY_ERROR_SYNC 0x00600000 +#define RTAS_LOG_SEVERITY_WARNING 0x00400000 +#define RTAS_LOG_SEVERITY_EVENT 0x00200000 +#define RTAS_LOG_SEVERITY_NO_ERROR 0x00000000 +#define RTAS_LOG_DISPOSITION_MASK 0x00180000 +#define RTAS_LOG_DISPOSITION_FULLY_RECOVERED 0x00000000 +#define RTAS_LOG_DISPOSITION_LIMITED_RECOVERY 0x00080000 +#define RTAS_LOG_DISPOSITION_NOT_RECOVERED 0x00100000 +#define RTAS_LOG_OPTIONAL_PART_PRESENT 0x00040000 +#define RTAS_LOG_INITIATOR_MASK 0x0000f000 +#define RTAS_LOG_INITIATOR_UNKNOWN 0x00000000 +#define RTAS_LOG_INITIATOR_CPU 0x00001000 +#define RTAS_LOG_INITIATOR_PCI 0x00002000 +#define RTAS_LOG_INITIATOR_MEMORY 0x00004000 +#define RTAS_LOG_INITIATOR_HOTPLUG 0x00006000 +#define RTAS_LOG_TARGET_MASK 0x00000f00 +#define RTAS_LOG_TARGET_UNKNOWN 0x00000000 +#define RTAS_LOG_TARGET_CPU 0x00000100 +#define RTAS_LOG_TARGET_PCI 0x00000200 +#define RTAS_LOG_TARGET_MEMORY 0x00000400 +#define RTAS_LOG_TARGET_HOTPLUG 0x00000600 +#define RTAS_LOG_TYPE_MASK 0x000000ff +#define RTAS_LOG_TYPE_OTHER 0x00000000 +#define RTAS_LOG_TYPE_RETRY 0x00000001 +#define RTAS_LOG_TYPE_TCE_ERR 0x00000002 +#define RTAS_LOG_TYPE_INTERN_DEV_FAIL 0x00000003 +#define RTAS_LOG_TYPE_TIMEOUT 0x00000004 +#define RTAS_LOG_TYPE_DATA_PARITY 0x00000005 +#define RTAS_LOG_TYPE_ADDR_PARITY 0x00000006 +#define RTAS_LOG_TYPE_CACHE_PARITY 0x00000007 +#define RTAS_LOG_TYPE_ADDR_INVALID 0x00000008 +#define RTAS_LOG_TYPE_ECC_UNCORR 0x00000009 +#define RTAS_LOG_TYPE_ECC_CORR 0x0000000a +#define RTAS_LOG_TYPE_EPOW 0x00000040 + uint32_t extended_length; +} QEMU_PACKED; + +struct rtas_event_log_v6 { + uint8_t b0; +#define RTAS_LOG_V6_B0_VALID 0x80 +#define RTAS_LOG_V6_B0_UNRECOVERABLE_ERROR 0x40 +#define RTAS_LOG_V6_B0_RECOVERABLE_ERROR 0x20 +#define RTAS_LOG_V6_B0_DEGRADED_OPERATION 0x10 +#define RTAS_LOG_V6_B0_PREDICTIVE_ERROR 0x08 +#define RTAS_LOG_V6_B0_NEW_LOG 0x04 +#define RTAS_LOG_V6_B0_BIGENDIAN 0x02 + uint8_t _resv1; + uint8_t b2; +#define RTAS_LOG_V6_B2_POWERPC_FORMAT 0x80 +#define RTAS_LOG_V6_B2_LOG_FORMAT_MASK 0x0f +#define RTAS_LOG_V6_B2_LOG_FORMAT_PLATFORM_EVENT 0x0e + uint8_t _resv2[9]; + uint32_t company; +#define RTAS_LOG_V6_COMPANY_IBM 0x49424d00 /* IBM<null> */ +} QEMU_PACKED; + +struct rtas_event_log_v6_section_header { + uint16_t section_id; + uint16_t section_length; + uint8_t section_version; + uint8_t section_subtype; + uint16_t creator_component_id; +} QEMU_PACKED; + +struct rtas_event_log_v6_maina { +#define RTAS_LOG_V6_SECTION_ID_MAINA 0x5048 /* PH */ + struct rtas_event_log_v6_section_header hdr; + uint32_t creation_date; /* BCD: YYYYMMDD */ + uint32_t creation_time; /* BCD: HHMMSS00 */ + uint8_t _platform1[8]; + char creator_id; + uint8_t _resv1[2]; + uint8_t section_count; + uint8_t _resv2[4]; + uint8_t _platform2[8]; + uint32_t plid; + uint8_t _platform3[4]; +} QEMU_PACKED; + +struct rtas_event_log_v6_mainb { +#define RTAS_LOG_V6_SECTION_ID_MAINB 0x5548 /* UH */ + struct rtas_event_log_v6_section_header hdr; + uint8_t subsystem_id; + uint8_t _platform1; + uint8_t event_severity; + uint8_t event_subtype; + uint8_t _platform2[4]; + uint8_t _resv1[2]; + uint16_t action_flags; + uint8_t _resv2[4]; +} QEMU_PACKED; + +struct rtas_event_log_v6_epow { +#define RTAS_LOG_V6_SECTION_ID_EPOW 0x4550 /* EP */ + struct rtas_event_log_v6_section_header hdr; + uint8_t sensor_value; +#define RTAS_LOG_V6_EPOW_ACTION_RESET 0 +#define RTAS_LOG_V6_EPOW_ACTION_WARN_COOLING 1 +#define RTAS_LOG_V6_EPOW_ACTION_WARN_POWER 2 +#define RTAS_LOG_V6_EPOW_ACTION_SYSTEM_SHUTDOWN 3 +#define RTAS_LOG_V6_EPOW_ACTION_SYSTEM_HALT 4 +#define RTAS_LOG_V6_EPOW_ACTION_MAIN_ENCLOSURE 5 +#define RTAS_LOG_V6_EPOW_ACTION_POWER_OFF 7 + uint8_t event_modifier; +#define RTAS_LOG_V6_EPOW_MODIFIER_NORMAL 1 +#define RTAS_LOG_V6_EPOW_MODIFIER_ON_UPS 2 +#define RTAS_LOG_V6_EPOW_MODIFIER_CRITICAL 3 +#define RTAS_LOG_V6_EPOW_MODIFIER_TEMPERATURE 4 + uint8_t extended_modifier; +#define RTAS_LOG_V6_EPOW_XMODIFIER_SYSTEM_WIDE 0 +#define RTAS_LOG_V6_EPOW_XMODIFIER_PARTITION_SPECIFIC 1 + uint8_t _resv; + uint64_t reason_code; +} QEMU_PACKED; + +struct epow_log_full { + struct rtas_error_log hdr; + struct rtas_event_log_v6 v6hdr; + struct rtas_event_log_v6_maina maina; + struct rtas_event_log_v6_mainb mainb; + struct rtas_event_log_v6_epow epow; +} QEMU_PACKED; + +#define EVENT_MASK_INTERNAL_ERRORS 0x80000000 +#define EVENT_MASK_EPOW 0x40000000 +#define EVENT_MASK_HOTPLUG 0x10000000 +#define EVENT_MASK_IO 0x08000000 + +#define _FDT(exp) \ + do { \ + int ret = (exp); \ + if (ret < 0) { \ + fprintf(stderr, "qemu: error creating device tree: %s: %s\n", \ + #exp, fdt_strerror(ret)); \ + exit(1); \ + } \ + } while (0) + +void spapr_events_fdt_skel(void *fdt, uint32_t epow_irq) +{ + uint32_t epow_irq_ranges[] = {cpu_to_be32(epow_irq), cpu_to_be32(1)}; + uint32_t epow_interrupts[] = {cpu_to_be32(epow_irq), 0}; + + _FDT((fdt_begin_node(fdt, "event-sources"))); + + _FDT((fdt_property(fdt, "interrupt-controller", NULL, 0))); + _FDT((fdt_property_cell(fdt, "#interrupt-cells", 2))); + _FDT((fdt_property(fdt, "interrupt-ranges", + epow_irq_ranges, sizeof(epow_irq_ranges)))); + + _FDT((fdt_begin_node(fdt, "epow-events"))); + _FDT((fdt_property(fdt, "interrupts", + epow_interrupts, sizeof(epow_interrupts)))); + _FDT((fdt_end_node(fdt))); + + _FDT((fdt_end_node(fdt))); +} + +static struct epow_log_full *pending_epow; +static uint32_t next_plid; + +static void spapr_powerdown_req(Notifier *n, void *opaque) +{ + sPAPREnvironment *spapr = container_of(n, sPAPREnvironment, epow_notifier); + struct rtas_error_log *hdr; + struct rtas_event_log_v6 *v6hdr; + struct rtas_event_log_v6_maina *maina; + struct rtas_event_log_v6_mainb *mainb; + struct rtas_event_log_v6_epow *epow; + struct tm tm; + int year; + + if (pending_epow) { + /* For now, we just throw away earlier events if two come + * along before any are consumed. This is sufficient for our + * powerdown messages, but we'll need more if we do more + * general error/event logging */ + g_free(pending_epow); + } + pending_epow = g_malloc0(sizeof(*pending_epow)); + hdr = &pending_epow->hdr; + v6hdr = &pending_epow->v6hdr; + maina = &pending_epow->maina; + mainb = &pending_epow->mainb; + epow = &pending_epow->epow; + + hdr->summary = cpu_to_be32(RTAS_LOG_VERSION_6 + | RTAS_LOG_SEVERITY_EVENT + | RTAS_LOG_DISPOSITION_NOT_RECOVERED + | RTAS_LOG_OPTIONAL_PART_PRESENT + | RTAS_LOG_TYPE_EPOW); + hdr->extended_length = cpu_to_be32(sizeof(*pending_epow) + - sizeof(pending_epow->hdr)); + + v6hdr->b0 = RTAS_LOG_V6_B0_VALID | RTAS_LOG_V6_B0_NEW_LOG + | RTAS_LOG_V6_B0_BIGENDIAN; + v6hdr->b2 = RTAS_LOG_V6_B2_POWERPC_FORMAT + | RTAS_LOG_V6_B2_LOG_FORMAT_PLATFORM_EVENT; + v6hdr->company = cpu_to_be32(RTAS_LOG_V6_COMPANY_IBM); + + maina->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_MAINA); + maina->hdr.section_length = cpu_to_be16(sizeof(*maina)); + /* FIXME: section version, subtype and creator id? */ + qemu_get_timedate(&tm, spapr->rtc_offset); + year = tm.tm_year + 1900; + maina->creation_date = cpu_to_be32((to_bcd(year / 100) << 24) + | (to_bcd(year % 100) << 16) + | (to_bcd(tm.tm_mon + 1) << 8) + | to_bcd(tm.tm_mday)); + maina->creation_time = cpu_to_be32((to_bcd(tm.tm_hour) << 24) + | (to_bcd(tm.tm_min) << 16) + | (to_bcd(tm.tm_sec) << 8)); + maina->creator_id = 'H'; /* Hypervisor */ + maina->section_count = 3; /* Main-A, Main-B and EPOW */ + maina->plid = next_plid++; + + mainb->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_MAINB); + mainb->hdr.section_length = cpu_to_be16(sizeof(*mainb)); + /* FIXME: section version, subtype and creator id? */ + mainb->subsystem_id = 0xa0; /* External environment */ + mainb->event_severity = 0x00; /* Informational / non-error */ + mainb->event_subtype = 0xd0; /* Normal shutdown */ + + epow->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_EPOW); + epow->hdr.section_length = cpu_to_be16(sizeof(*epow)); + epow->hdr.section_version = 2; /* includes extended modifier */ + /* FIXME: section subtype and creator id? */ + epow->sensor_value = RTAS_LOG_V6_EPOW_ACTION_SYSTEM_SHUTDOWN; + epow->event_modifier = RTAS_LOG_V6_EPOW_MODIFIER_NORMAL; + epow->extended_modifier = RTAS_LOG_V6_EPOW_XMODIFIER_PARTITION_SPECIFIC; + + qemu_irq_pulse(xics_get_qirq(spapr->icp, spapr->epow_irq)); +} + +static void check_exception(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + uint32_t mask, buf, len; + uint64_t xinfo; + + if ((nargs < 6) || (nargs > 7) || nret != 1) { + rtas_st(rets, 0, -3); + return; + } + + xinfo = rtas_ld(args, 1); + mask = rtas_ld(args, 2); + buf = rtas_ld(args, 4); + len = rtas_ld(args, 5); + if (nargs == 7) { + xinfo |= (uint64_t)rtas_ld(args, 6) << 32; + } + + if ((mask & EVENT_MASK_EPOW) && pending_epow) { + if (sizeof(*pending_epow) < len) { + len = sizeof(*pending_epow); + } + + cpu_physical_memory_write(buf, pending_epow, len); + g_free(pending_epow); + pending_epow = NULL; + rtas_st(rets, 0, 0); + } else { + rtas_st(rets, 0, 1); + } +} + +void spapr_events_init(sPAPREnvironment *spapr) +{ + spapr->epow_irq = spapr_allocate_msi(0); + spapr->epow_notifier.notify = spapr_powerdown_req; + qemu_register_powerdown_notifier(&spapr->epow_notifier); + spapr_rtas_register("check-exception", check_exception); +} diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c new file mode 100644 index 0000000000..7b8959488e --- /dev/null +++ b/hw/ppc/spapr_hcall.c @@ -0,0 +1,741 @@ +#include "sysemu/sysemu.h" +#include "cpu.h" +#include "sysemu/sysemu.h" +#include "helper_regs.h" +#include "hw/spapr.h" + +#define HPTES_PER_GROUP 8 + +#define HPTE_V_SSIZE_SHIFT 62 +#define HPTE_V_AVPN_SHIFT 7 +#define HPTE_V_AVPN 0x3fffffffffffff80ULL +#define HPTE_V_AVPN_VAL(x) (((x) & HPTE_V_AVPN) >> HPTE_V_AVPN_SHIFT) +#define HPTE_V_COMPARE(x, y) (!(((x) ^ (y)) & 0xffffffffffffff80UL)) +#define HPTE_V_BOLTED 0x0000000000000010ULL +#define HPTE_V_LOCK 0x0000000000000008ULL +#define HPTE_V_LARGE 0x0000000000000004ULL +#define HPTE_V_SECONDARY 0x0000000000000002ULL +#define HPTE_V_VALID 0x0000000000000001ULL + +#define HPTE_R_PP0 0x8000000000000000ULL +#define HPTE_R_TS 0x4000000000000000ULL +#define HPTE_R_KEY_HI 0x3000000000000000ULL +#define HPTE_R_RPN_SHIFT 12 +#define HPTE_R_RPN 0x3ffffffffffff000ULL +#define HPTE_R_FLAGS 0x00000000000003ffULL +#define HPTE_R_PP 0x0000000000000003ULL +#define HPTE_R_N 0x0000000000000004ULL +#define HPTE_R_G 0x0000000000000008ULL +#define HPTE_R_M 0x0000000000000010ULL +#define HPTE_R_I 0x0000000000000020ULL +#define HPTE_R_W 0x0000000000000040ULL +#define HPTE_R_WIMG 0x0000000000000078ULL +#define HPTE_R_C 0x0000000000000080ULL +#define HPTE_R_R 0x0000000000000100ULL +#define HPTE_R_KEY_LO 0x0000000000000e00ULL + +#define HPTE_V_1TB_SEG 0x4000000000000000ULL +#define HPTE_V_VRMA_MASK 0x4001ffffff000000ULL + +static target_ulong compute_tlbie_rb(target_ulong v, target_ulong r, + target_ulong pte_index) +{ + target_ulong rb, va_low; + + rb = (v & ~0x7fULL) << 16; /* AVA field */ + va_low = pte_index >> 3; + if (v & HPTE_V_SECONDARY) { + va_low = ~va_low; + } + /* xor vsid from AVA */ + if (!(v & HPTE_V_1TB_SEG)) { + va_low ^= v >> 12; + } else { + va_low ^= v >> 24; + } + va_low &= 0x7ff; + if (v & HPTE_V_LARGE) { + rb |= 1; /* L field */ +#if 0 /* Disable that P7 specific bit for now */ + if (r & 0xff000) { + /* non-16MB large page, must be 64k */ + /* (masks depend on page size) */ + rb |= 0x1000; /* page encoding in LP field */ + rb |= (va_low & 0x7f) << 16; /* 7b of VA in AVA/LP field */ + rb |= (va_low & 0xfe); /* AVAL field */ + } +#endif + } else { + /* 4kB page */ + rb |= (va_low & 0x7ff) << 12; /* remaining 11b of AVA */ + } + rb |= (v >> 54) & 0x300; /* B field */ + return rb; +} + +static target_ulong h_enter(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + CPUPPCState *env = &cpu->env; + target_ulong flags = args[0]; + target_ulong pte_index = args[1]; + target_ulong pteh = args[2]; + target_ulong ptel = args[3]; + target_ulong page_shift = 12; + target_ulong raddr; + target_ulong i; + uint8_t *hpte; + + /* only handle 4k and 16M pages for now */ + if (pteh & HPTE_V_LARGE) { +#if 0 /* We don't support 64k pages yet */ + if ((ptel & 0xf000) == 0x1000) { + /* 64k page */ + } else +#endif + if ((ptel & 0xff000) == 0) { + /* 16M page */ + page_shift = 24; + /* lowest AVA bit must be 0 for 16M pages */ + if (pteh & 0x80) { + return H_PARAMETER; + } + } else { + return H_PARAMETER; + } + } + + raddr = (ptel & HPTE_R_RPN) & ~((1ULL << page_shift) - 1); + + if (raddr < spapr->ram_limit) { + /* Regular RAM - should have WIMG=0010 */ + if ((ptel & HPTE_R_WIMG) != HPTE_R_M) { + return H_PARAMETER; + } + } else { + /* Looks like an IO address */ + /* FIXME: What WIMG combinations could be sensible for IO? + * For now we allow WIMG=010x, but are there others? */ + /* FIXME: Should we check against registered IO addresses? */ + if ((ptel & (HPTE_R_W | HPTE_R_I | HPTE_R_M)) != HPTE_R_I) { + return H_PARAMETER; + } + } + + pteh &= ~0x60ULL; + + if ((pte_index * HASH_PTE_SIZE_64) & ~env->htab_mask) { + return H_PARAMETER; + } + if (likely((flags & H_EXACT) == 0)) { + pte_index &= ~7ULL; + hpte = env->external_htab + (pte_index * HASH_PTE_SIZE_64); + for (i = 0; ; ++i) { + if (i == 8) { + return H_PTEG_FULL; + } + if ((ldq_p(hpte) & HPTE_V_VALID) == 0) { + break; + } + hpte += HASH_PTE_SIZE_64; + } + } else { + i = 0; + hpte = env->external_htab + (pte_index * HASH_PTE_SIZE_64); + if (ldq_p(hpte) & HPTE_V_VALID) { + return H_PTEG_FULL; + } + } + stq_p(hpte + (HASH_PTE_SIZE_64/2), ptel); + /* eieio(); FIXME: need some sort of barrier for smp? */ + stq_p(hpte, pteh); + + args[0] = pte_index + i; + return H_SUCCESS; +} + +enum { + REMOVE_SUCCESS = 0, + REMOVE_NOT_FOUND = 1, + REMOVE_PARM = 2, + REMOVE_HW = 3, +}; + +static target_ulong remove_hpte(CPUPPCState *env, target_ulong ptex, + target_ulong avpn, + target_ulong flags, + target_ulong *vp, target_ulong *rp) +{ + uint8_t *hpte; + target_ulong v, r, rb; + + if ((ptex * HASH_PTE_SIZE_64) & ~env->htab_mask) { + return REMOVE_PARM; + } + + hpte = env->external_htab + (ptex * HASH_PTE_SIZE_64); + + v = ldq_p(hpte); + r = ldq_p(hpte + (HASH_PTE_SIZE_64/2)); + + if ((v & HPTE_V_VALID) == 0 || + ((flags & H_AVPN) && (v & ~0x7fULL) != avpn) || + ((flags & H_ANDCOND) && (v & avpn) != 0)) { + return REMOVE_NOT_FOUND; + } + *vp = v; + *rp = r; + stq_p(hpte, 0); + rb = compute_tlbie_rb(v, r, ptex); + ppc_tlb_invalidate_one(env, rb); + return REMOVE_SUCCESS; +} + +static target_ulong h_remove(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + CPUPPCState *env = &cpu->env; + target_ulong flags = args[0]; + target_ulong pte_index = args[1]; + target_ulong avpn = args[2]; + int ret; + + ret = remove_hpte(env, pte_index, avpn, flags, + &args[0], &args[1]); + + switch (ret) { + case REMOVE_SUCCESS: + return H_SUCCESS; + + case REMOVE_NOT_FOUND: + return H_NOT_FOUND; + + case REMOVE_PARM: + return H_PARAMETER; + + case REMOVE_HW: + return H_HARDWARE; + } + + assert(0); +} + +#define H_BULK_REMOVE_TYPE 0xc000000000000000ULL +#define H_BULK_REMOVE_REQUEST 0x4000000000000000ULL +#define H_BULK_REMOVE_RESPONSE 0x8000000000000000ULL +#define H_BULK_REMOVE_END 0xc000000000000000ULL +#define H_BULK_REMOVE_CODE 0x3000000000000000ULL +#define H_BULK_REMOVE_SUCCESS 0x0000000000000000ULL +#define H_BULK_REMOVE_NOT_FOUND 0x1000000000000000ULL +#define H_BULK_REMOVE_PARM 0x2000000000000000ULL +#define H_BULK_REMOVE_HW 0x3000000000000000ULL +#define H_BULK_REMOVE_RC 0x0c00000000000000ULL +#define H_BULK_REMOVE_FLAGS 0x0300000000000000ULL +#define H_BULK_REMOVE_ABSOLUTE 0x0000000000000000ULL +#define H_BULK_REMOVE_ANDCOND 0x0100000000000000ULL +#define H_BULK_REMOVE_AVPN 0x0200000000000000ULL +#define H_BULK_REMOVE_PTEX 0x00ffffffffffffffULL + +#define H_BULK_REMOVE_MAX_BATCH 4 + +static target_ulong h_bulk_remove(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + CPUPPCState *env = &cpu->env; + int i; + + for (i = 0; i < H_BULK_REMOVE_MAX_BATCH; i++) { + target_ulong *tsh = &args[i*2]; + target_ulong tsl = args[i*2 + 1]; + target_ulong v, r, ret; + + if ((*tsh & H_BULK_REMOVE_TYPE) == H_BULK_REMOVE_END) { + break; + } else if ((*tsh & H_BULK_REMOVE_TYPE) != H_BULK_REMOVE_REQUEST) { + return H_PARAMETER; + } + + *tsh &= H_BULK_REMOVE_PTEX | H_BULK_REMOVE_FLAGS; + *tsh |= H_BULK_REMOVE_RESPONSE; + + if ((*tsh & H_BULK_REMOVE_ANDCOND) && (*tsh & H_BULK_REMOVE_AVPN)) { + *tsh |= H_BULK_REMOVE_PARM; + return H_PARAMETER; + } + + ret = remove_hpte(env, *tsh & H_BULK_REMOVE_PTEX, tsl, + (*tsh & H_BULK_REMOVE_FLAGS) >> 26, + &v, &r); + + *tsh |= ret << 60; + + switch (ret) { + case REMOVE_SUCCESS: + *tsh |= (r & (HPTE_R_C | HPTE_R_R)) << 43; + break; + + case REMOVE_PARM: + return H_PARAMETER; + + case REMOVE_HW: + return H_HARDWARE; + } + } + + return H_SUCCESS; +} + +static target_ulong h_protect(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + CPUPPCState *env = &cpu->env; + target_ulong flags = args[0]; + target_ulong pte_index = args[1]; + target_ulong avpn = args[2]; + uint8_t *hpte; + target_ulong v, r, rb; + + if ((pte_index * HASH_PTE_SIZE_64) & ~env->htab_mask) { + return H_PARAMETER; + } + + hpte = env->external_htab + (pte_index * HASH_PTE_SIZE_64); + + v = ldq_p(hpte); + r = ldq_p(hpte + (HASH_PTE_SIZE_64/2)); + + if ((v & HPTE_V_VALID) == 0 || + ((flags & H_AVPN) && (v & ~0x7fULL) != avpn)) { + return H_NOT_FOUND; + } + + r &= ~(HPTE_R_PP0 | HPTE_R_PP | HPTE_R_N | + HPTE_R_KEY_HI | HPTE_R_KEY_LO); + r |= (flags << 55) & HPTE_R_PP0; + r |= (flags << 48) & HPTE_R_KEY_HI; + r |= flags & (HPTE_R_PP | HPTE_R_N | HPTE_R_KEY_LO); + rb = compute_tlbie_rb(v, r, pte_index); + stq_p(hpte, v & ~HPTE_V_VALID); + ppc_tlb_invalidate_one(env, rb); + stq_p(hpte + (HASH_PTE_SIZE_64/2), r); + /* Don't need a memory barrier, due to qemu's global lock */ + stq_p(hpte, v); + return H_SUCCESS; +} + +static target_ulong h_set_dabr(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + /* FIXME: actually implement this */ + return H_HARDWARE; +} + +#define FLAGS_REGISTER_VPA 0x0000200000000000ULL +#define FLAGS_REGISTER_DTL 0x0000400000000000ULL +#define FLAGS_REGISTER_SLBSHADOW 0x0000600000000000ULL +#define FLAGS_DEREGISTER_VPA 0x0000a00000000000ULL +#define FLAGS_DEREGISTER_DTL 0x0000c00000000000ULL +#define FLAGS_DEREGISTER_SLBSHADOW 0x0000e00000000000ULL + +#define VPA_MIN_SIZE 640 +#define VPA_SIZE_OFFSET 0x4 +#define VPA_SHARED_PROC_OFFSET 0x9 +#define VPA_SHARED_PROC_VAL 0x2 + +static target_ulong register_vpa(CPUPPCState *env, target_ulong vpa) +{ + uint16_t size; + uint8_t tmp; + + if (vpa == 0) { + hcall_dprintf("Can't cope with registering a VPA at logical 0\n"); + return H_HARDWARE; + } + + if (vpa % env->dcache_line_size) { + return H_PARAMETER; + } + /* FIXME: bounds check the address */ + + size = lduw_be_phys(vpa + 0x4); + + if (size < VPA_MIN_SIZE) { + return H_PARAMETER; + } + + /* VPA is not allowed to cross a page boundary */ + if ((vpa / 4096) != ((vpa + size - 1) / 4096)) { + return H_PARAMETER; + } + + env->vpa_addr = vpa; + + tmp = ldub_phys(env->vpa_addr + VPA_SHARED_PROC_OFFSET); + tmp |= VPA_SHARED_PROC_VAL; + stb_phys(env->vpa_addr + VPA_SHARED_PROC_OFFSET, tmp); + + return H_SUCCESS; +} + +static target_ulong deregister_vpa(CPUPPCState *env, target_ulong vpa) +{ + if (env->slb_shadow_addr) { + return H_RESOURCE; + } + + if (env->dtl_addr) { + return H_RESOURCE; + } + + env->vpa_addr = 0; + return H_SUCCESS; +} + +static target_ulong register_slb_shadow(CPUPPCState *env, target_ulong addr) +{ + uint32_t size; + + if (addr == 0) { + hcall_dprintf("Can't cope with SLB shadow at logical 0\n"); + return H_HARDWARE; + } + + size = ldl_be_phys(addr + 0x4); + if (size < 0x8) { + return H_PARAMETER; + } + + if ((addr / 4096) != ((addr + size - 1) / 4096)) { + return H_PARAMETER; + } + + if (!env->vpa_addr) { + return H_RESOURCE; + } + + env->slb_shadow_addr = addr; + env->slb_shadow_size = size; + + return H_SUCCESS; +} + +static target_ulong deregister_slb_shadow(CPUPPCState *env, target_ulong addr) +{ + env->slb_shadow_addr = 0; + env->slb_shadow_size = 0; + return H_SUCCESS; +} + +static target_ulong register_dtl(CPUPPCState *env, target_ulong addr) +{ + uint32_t size; + + if (addr == 0) { + hcall_dprintf("Can't cope with DTL at logical 0\n"); + return H_HARDWARE; + } + + size = ldl_be_phys(addr + 0x4); + + if (size < 48) { + return H_PARAMETER; + } + + if (!env->vpa_addr) { + return H_RESOURCE; + } + + env->dtl_addr = addr; + env->dtl_size = size; + + return H_SUCCESS; +} + +static target_ulong deregister_dtl(CPUPPCState *env, target_ulong addr) +{ + env->dtl_addr = 0; + env->dtl_size = 0; + + return H_SUCCESS; +} + +static target_ulong h_register_vpa(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong flags = args[0]; + target_ulong procno = args[1]; + target_ulong vpa = args[2]; + target_ulong ret = H_PARAMETER; + CPUPPCState *tenv; + CPUState *tcpu; + + tcpu = qemu_get_cpu(procno); + if (!tcpu) { + return H_PARAMETER; + } + tenv = tcpu->env_ptr; + + switch (flags) { + case FLAGS_REGISTER_VPA: + ret = register_vpa(tenv, vpa); + break; + + case FLAGS_DEREGISTER_VPA: + ret = deregister_vpa(tenv, vpa); + break; + + case FLAGS_REGISTER_SLBSHADOW: + ret = register_slb_shadow(tenv, vpa); + break; + + case FLAGS_DEREGISTER_SLBSHADOW: + ret = deregister_slb_shadow(tenv, vpa); + break; + + case FLAGS_REGISTER_DTL: + ret = register_dtl(tenv, vpa); + break; + + case FLAGS_DEREGISTER_DTL: + ret = deregister_dtl(tenv, vpa); + break; + } + + return ret; +} + +static target_ulong h_cede(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + CPUPPCState *env = &cpu->env; + CPUState *cs = CPU(cpu); + + env->msr |= (1ULL << MSR_EE); + hreg_compute_hflags(env); + if (!cpu_has_work(cs)) { + env->halted = 1; + env->exception_index = EXCP_HLT; + cs->exit_request = 1; + } + return H_SUCCESS; +} + +static target_ulong h_rtas(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong rtas_r3 = args[0]; + uint32_t token = ldl_be_phys(rtas_r3); + uint32_t nargs = ldl_be_phys(rtas_r3 + 4); + uint32_t nret = ldl_be_phys(rtas_r3 + 8); + + return spapr_rtas_call(spapr, token, nargs, rtas_r3 + 12, + nret, rtas_r3 + 12 + 4*nargs); +} + +static target_ulong h_logical_load(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong size = args[0]; + target_ulong addr = args[1]; + + switch (size) { + case 1: + args[0] = ldub_phys(addr); + return H_SUCCESS; + case 2: + args[0] = lduw_phys(addr); + return H_SUCCESS; + case 4: + args[0] = ldl_phys(addr); + return H_SUCCESS; + case 8: + args[0] = ldq_phys(addr); + return H_SUCCESS; + } + return H_PARAMETER; +} + +static target_ulong h_logical_store(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong size = args[0]; + target_ulong addr = args[1]; + target_ulong val = args[2]; + + switch (size) { + case 1: + stb_phys(addr, val); + return H_SUCCESS; + case 2: + stw_phys(addr, val); + return H_SUCCESS; + case 4: + stl_phys(addr, val); + return H_SUCCESS; + case 8: + stq_phys(addr, val); + return H_SUCCESS; + } + return H_PARAMETER; +} + +static target_ulong h_logical_memop(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong dst = args[0]; /* Destination address */ + target_ulong src = args[1]; /* Source address */ + target_ulong esize = args[2]; /* Element size (0=1,1=2,2=4,3=8) */ + target_ulong count = args[3]; /* Element count */ + target_ulong op = args[4]; /* 0 = copy, 1 = invert */ + uint64_t tmp; + unsigned int mask = (1 << esize) - 1; + int step = 1 << esize; + + if (count > 0x80000000) { + return H_PARAMETER; + } + + if ((dst & mask) || (src & mask) || (op > 1)) { + return H_PARAMETER; + } + + if (dst >= src && dst < (src + (count << esize))) { + dst = dst + ((count - 1) << esize); + src = src + ((count - 1) << esize); + step = -step; + } + + while (count--) { + switch (esize) { + case 0: + tmp = ldub_phys(src); + break; + case 1: + tmp = lduw_phys(src); + break; + case 2: + tmp = ldl_phys(src); + break; + case 3: + tmp = ldq_phys(src); + break; + default: + return H_PARAMETER; + } + if (op == 1) { + tmp = ~tmp; + } + switch (esize) { + case 0: + stb_phys(dst, tmp); + break; + case 1: + stw_phys(dst, tmp); + break; + case 2: + stl_phys(dst, tmp); + break; + case 3: + stq_phys(dst, tmp); + break; + } + dst = dst + step; + src = src + step; + } + + return H_SUCCESS; +} + +static target_ulong h_logical_icbi(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + /* Nothing to do on emulation, KVM will trap this in the kernel */ + return H_SUCCESS; +} + +static target_ulong h_logical_dcbf(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + /* Nothing to do on emulation, KVM will trap this in the kernel */ + return H_SUCCESS; +} + +static spapr_hcall_fn papr_hypercall_table[(MAX_HCALL_OPCODE / 4) + 1]; +static spapr_hcall_fn kvmppc_hypercall_table[KVMPPC_HCALL_MAX - KVMPPC_HCALL_BASE + 1]; + +void spapr_register_hypercall(target_ulong opcode, spapr_hcall_fn fn) +{ + spapr_hcall_fn *slot; + + if (opcode <= MAX_HCALL_OPCODE) { + assert((opcode & 0x3) == 0); + + slot = &papr_hypercall_table[opcode / 4]; + } else { + assert((opcode >= KVMPPC_HCALL_BASE) && (opcode <= KVMPPC_HCALL_MAX)); + + slot = &kvmppc_hypercall_table[opcode - KVMPPC_HCALL_BASE]; + } + + assert(!(*slot)); + *slot = fn; +} + +target_ulong spapr_hypercall(PowerPCCPU *cpu, target_ulong opcode, + target_ulong *args) +{ + if ((opcode <= MAX_HCALL_OPCODE) + && ((opcode & 0x3) == 0)) { + spapr_hcall_fn fn = papr_hypercall_table[opcode / 4]; + + if (fn) { + return fn(cpu, spapr, opcode, args); + } + } else if ((opcode >= KVMPPC_HCALL_BASE) && + (opcode <= KVMPPC_HCALL_MAX)) { + spapr_hcall_fn fn = kvmppc_hypercall_table[opcode - KVMPPC_HCALL_BASE]; + + if (fn) { + return fn(cpu, spapr, opcode, args); + } + } + + hcall_dprintf("Unimplemented hcall 0x" TARGET_FMT_lx "\n", opcode); + return H_FUNCTION; +} + +static void hypercall_register_types(void) +{ + /* hcall-pft */ + spapr_register_hypercall(H_ENTER, h_enter); + spapr_register_hypercall(H_REMOVE, h_remove); + spapr_register_hypercall(H_PROTECT, h_protect); + + /* hcall-bulk */ + spapr_register_hypercall(H_BULK_REMOVE, h_bulk_remove); + + /* hcall-dabr */ + spapr_register_hypercall(H_SET_DABR, h_set_dabr); + + /* hcall-splpar */ + spapr_register_hypercall(H_REGISTER_VPA, h_register_vpa); + spapr_register_hypercall(H_CEDE, h_cede); + + /* "debugger" hcalls (also used by SLOF). Note: We do -not- differenciate + * here between the "CI" and the "CACHE" variants, they will use whatever + * mapping attributes qemu is using. When using KVM, the kernel will + * enforce the attributes more strongly + */ + spapr_register_hypercall(H_LOGICAL_CI_LOAD, h_logical_load); + spapr_register_hypercall(H_LOGICAL_CI_STORE, h_logical_store); + spapr_register_hypercall(H_LOGICAL_CACHE_LOAD, h_logical_load); + spapr_register_hypercall(H_LOGICAL_CACHE_STORE, h_logical_store); + spapr_register_hypercall(H_LOGICAL_ICBI, h_logical_icbi); + spapr_register_hypercall(H_LOGICAL_DCBF, h_logical_dcbf); + spapr_register_hypercall(KVMPPC_H_LOGICAL_MEMOP, h_logical_memop); + + /* qemu/KVM-PPC specific hcalls */ + spapr_register_hypercall(KVMPPC_H_RTAS, h_rtas); +} + +type_init(hypercall_register_types) diff --git a/hw/ppc/spapr_iommu.c b/hw/ppc/spapr_iommu.c new file mode 100644 index 0000000000..8d500bf6be --- /dev/null +++ b/hw/ppc/spapr_iommu.c @@ -0,0 +1,293 @@ +/* + * QEMU sPAPR IOMMU (TCE) code + * + * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ +#include "hw/hw.h" +#include "sysemu/kvm.h" +#include "hw/qdev.h" +#include "kvm_ppc.h" +#include "sysemu/dma.h" +#include "exec/address-spaces.h" + +#include "hw/spapr.h" + +#include <libfdt.h> + +/* #define DEBUG_TCE */ + +enum sPAPRTCEAccess { + SPAPR_TCE_FAULT = 0, + SPAPR_TCE_RO = 1, + SPAPR_TCE_WO = 2, + SPAPR_TCE_RW = 3, +}; + +typedef struct sPAPRTCETable sPAPRTCETable; + +struct sPAPRTCETable { + DMAContext dma; + uint32_t liobn; + uint32_t window_size; + sPAPRTCE *table; + bool bypass; + int fd; + QLIST_ENTRY(sPAPRTCETable) list; +}; + + +QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables; + +static sPAPRTCETable *spapr_tce_find_by_liobn(uint32_t liobn) +{ + sPAPRTCETable *tcet; + + QLIST_FOREACH(tcet, &spapr_tce_tables, list) { + if (tcet->liobn == liobn) { + return tcet; + } + } + + return NULL; +} + +static int spapr_tce_translate(DMAContext *dma, + dma_addr_t addr, + hwaddr *paddr, + hwaddr *len, + DMADirection dir) +{ + sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma); + enum sPAPRTCEAccess access = (dir == DMA_DIRECTION_FROM_DEVICE) + ? SPAPR_TCE_WO : SPAPR_TCE_RO; + uint64_t tce; + +#ifdef DEBUG_TCE + fprintf(stderr, "spapr_tce_translate liobn=0x%" PRIx32 " addr=0x" + DMA_ADDR_FMT "\n", tcet->liobn, addr); +#endif + + if (tcet->bypass) { + *paddr = addr; + *len = (hwaddr)-1; + return 0; + } + + /* Check if we are in bound */ + if (addr >= tcet->window_size) { +#ifdef DEBUG_TCE + fprintf(stderr, "spapr_tce_translate out of bounds\n"); +#endif + return -EFAULT; + } + + tce = tcet->table[addr >> SPAPR_TCE_PAGE_SHIFT].tce; + + /* Check TCE */ + if (!(tce & access)) { + return -EPERM; + } + + /* How much til end of page ? */ + *len = ((~addr) & SPAPR_TCE_PAGE_MASK) + 1; + + /* Translate */ + *paddr = (tce & ~SPAPR_TCE_PAGE_MASK) | + (addr & SPAPR_TCE_PAGE_MASK); + +#ifdef DEBUG_TCE + fprintf(stderr, " -> *paddr=0x" TARGET_FMT_plx ", *len=0x" + TARGET_FMT_plx "\n", *paddr, *len); +#endif + + return 0; +} + +DMAContext *spapr_tce_new_dma_context(uint32_t liobn, size_t window_size) +{ + sPAPRTCETable *tcet; + + if (spapr_tce_find_by_liobn(liobn)) { + fprintf(stderr, "Attempted to create TCE table with duplicate" + " LIOBN 0x%x\n", liobn); + return NULL; + } + + if (!window_size) { + return NULL; + } + + tcet = g_malloc0(sizeof(*tcet)); + dma_context_init(&tcet->dma, &address_space_memory, spapr_tce_translate, NULL, NULL); + + tcet->liobn = liobn; + tcet->window_size = window_size; + + if (kvm_enabled()) { + tcet->table = kvmppc_create_spapr_tce(liobn, + window_size, + &tcet->fd); + } + + if (!tcet->table) { + size_t table_size = (window_size >> SPAPR_TCE_PAGE_SHIFT) + * sizeof(sPAPRTCE); + tcet->table = g_malloc0(table_size); + } + +#ifdef DEBUG_TCE + fprintf(stderr, "spapr_iommu: New TCE table, liobn=0x%x, context @ %p, " + "table @ %p, fd=%d\n", liobn, &tcet->dma, tcet->table, tcet->fd); +#endif + + QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list); + + return &tcet->dma; +} + +void spapr_tce_free(DMAContext *dma) +{ + + if (dma) { + sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma); + + QLIST_REMOVE(tcet, list); + + if (!kvm_enabled() || + (kvmppc_remove_spapr_tce(tcet->table, tcet->fd, + tcet->window_size) != 0)) { + g_free(tcet->table); + } + + g_free(tcet); + } +} + +void spapr_tce_set_bypass(DMAContext *dma, bool bypass) +{ + sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma); + + tcet->bypass = bypass; +} + +void spapr_tce_reset(DMAContext *dma) +{ + sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma); + size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT) + * sizeof(sPAPRTCE); + + tcet->bypass = false; + memset(tcet->table, 0, table_size); +} + +static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba, + target_ulong tce) +{ + sPAPRTCE *tcep; + + if (ioba >= tcet->window_size) { + hcall_dprintf("spapr_vio_put_tce on out-of-boards IOBA 0x" + TARGET_FMT_lx "\n", ioba); + return H_PARAMETER; + } + + tcep = tcet->table + (ioba >> SPAPR_TCE_PAGE_SHIFT); + tcep->tce = tce; + + return H_SUCCESS; +} + +static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong liobn = args[0]; + target_ulong ioba = args[1]; + target_ulong tce = args[2]; + sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn); + + if (liobn & 0xFFFFFFFF00000000ULL) { + hcall_dprintf("spapr_vio_put_tce on out-of-boundsw LIOBN " + TARGET_FMT_lx "\n", liobn); + return H_PARAMETER; + } + + ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1); + + if (tcet) { + return put_tce_emu(tcet, ioba, tce); + } +#ifdef DEBUG_TCE + fprintf(stderr, "%s on liobn=" TARGET_FMT_lx /*%s*/ + " ioba 0x" TARGET_FMT_lx " TCE 0x" TARGET_FMT_lx "\n", + __func__, liobn, /*dev->qdev.id, */ioba, tce); +#endif + + return H_PARAMETER; +} + +void spapr_iommu_init(void) +{ + QLIST_INIT(&spapr_tce_tables); + + /* hcall-tce */ + spapr_register_hypercall(H_PUT_TCE, h_put_tce); +} + +int spapr_dma_dt(void *fdt, int node_off, const char *propname, + uint32_t liobn, uint64_t window, uint32_t size) +{ + uint32_t dma_prop[5]; + int ret; + + dma_prop[0] = cpu_to_be32(liobn); + dma_prop[1] = cpu_to_be32(window >> 32); + dma_prop[2] = cpu_to_be32(window & 0xFFFFFFFF); + dma_prop[3] = 0; /* window size is 32 bits */ + dma_prop[4] = cpu_to_be32(size); + + ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-address-cells", 2); + if (ret < 0) { + return ret; + } + + ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-size-cells", 2); + if (ret < 0) { + return ret; + } + + ret = fdt_setprop(fdt, node_off, propname, dma_prop, sizeof(dma_prop)); + if (ret < 0) { + return ret; + } + + return 0; +} + +int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname, + DMAContext *iommu) +{ + if (!iommu) { + return 0; + } + + if (iommu->translate == spapr_tce_translate) { + sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, iommu); + return spapr_dma_dt(fdt, node_off, propname, + tcet->liobn, 0, tcet->window_size); + } + + return -1; +} diff --git a/hw/ppc/spapr_rtas.c b/hw/ppc/spapr_rtas.c new file mode 100644 index 0000000000..5ec787f29d --- /dev/null +++ b/hw/ppc/spapr_rtas.c @@ -0,0 +1,334 @@ +/* + * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator + * + * Hypercall based emulated RTAS + * + * Copyright (c) 2010-2011 David Gibson, IBM Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "cpu.h" +#include "sysemu/sysemu.h" +#include "char/char.h" +#include "hw/qdev.h" +#include "sysemu/device_tree.h" + +#include "hw/spapr.h" +#include "hw/spapr_vio.h" + +#include <libfdt.h> + +#define TOKEN_BASE 0x2000 +#define TOKEN_MAX 0x100 + +static void rtas_display_character(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + uint8_t c = rtas_ld(args, 0); + VIOsPAPRDevice *sdev = vty_lookup(spapr, 0); + + if (!sdev) { + rtas_st(rets, 0, -1); + } else { + vty_putchars(sdev, &c, sizeof(c)); + rtas_st(rets, 0, 0); + } +} + +static void rtas_get_time_of_day(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + struct tm tm; + + if (nret != 8) { + rtas_st(rets, 0, -3); + return; + } + + qemu_get_timedate(&tm, spapr->rtc_offset); + + rtas_st(rets, 0, 0); /* Success */ + rtas_st(rets, 1, tm.tm_year + 1900); + rtas_st(rets, 2, tm.tm_mon + 1); + rtas_st(rets, 3, tm.tm_mday); + rtas_st(rets, 4, tm.tm_hour); + rtas_st(rets, 5, tm.tm_min); + rtas_st(rets, 6, tm.tm_sec); + rtas_st(rets, 7, 0); /* we don't do nanoseconds */ +} + +static void rtas_set_time_of_day(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + struct tm tm; + + tm.tm_year = rtas_ld(args, 0) - 1900; + tm.tm_mon = rtas_ld(args, 1) - 1; + tm.tm_mday = rtas_ld(args, 2); + tm.tm_hour = rtas_ld(args, 3); + tm.tm_min = rtas_ld(args, 4); + tm.tm_sec = rtas_ld(args, 5); + + /* Just generate a monitor event for the change */ + rtc_change_mon_event(&tm); + spapr->rtc_offset = qemu_timedate_diff(&tm); + + rtas_st(rets, 0, 0); /* Success */ +} + +static void rtas_power_off(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, target_ulong args, + uint32_t nret, target_ulong rets) +{ + if (nargs != 2 || nret != 1) { + rtas_st(rets, 0, -3); + return; + } + qemu_system_shutdown_request(); + rtas_st(rets, 0, 0); +} + +static void rtas_system_reboot(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + if (nargs != 0 || nret != 1) { + rtas_st(rets, 0, -3); + return; + } + qemu_system_reset_request(); + rtas_st(rets, 0, 0); +} + +static void rtas_query_cpu_stopped_state(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + target_ulong id; + CPUPPCState *env; + CPUState *cpu; + + if (nargs != 1 || nret != 2) { + rtas_st(rets, 0, -3); + return; + } + + id = rtas_ld(args, 0); + for (env = first_cpu; env; env = env->next_cpu) { + cpu = CPU(ppc_env_get_cpu(env)); + if (cpu->cpu_index != id) { + continue; + } + + if (env->halted) { + rtas_st(rets, 1, 0); + } else { + rtas_st(rets, 1, 2); + } + + rtas_st(rets, 0, 0); + return; + } + + /* Didn't find a matching cpu */ + rtas_st(rets, 0, -3); +} + +static void rtas_start_cpu(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, + target_ulong args, + uint32_t nret, target_ulong rets) +{ + target_ulong id, start, r3; + CPUState *cpu; + CPUPPCState *env; + + if (nargs != 3 || nret != 1) { + rtas_st(rets, 0, -3); + return; + } + + id = rtas_ld(args, 0); + start = rtas_ld(args, 1); + r3 = rtas_ld(args, 2); + + for (env = first_cpu; env; env = env->next_cpu) { + cpu = CPU(ppc_env_get_cpu(env)); + + if (cpu->cpu_index != id) { + continue; + } + + if (!env->halted) { + rtas_st(rets, 0, -1); + return; + } + + /* This will make sure qemu state is up to date with kvm, and + * mark it dirty so our changes get flushed back before the + * new cpu enters */ + kvm_cpu_synchronize_state(env); + + env->msr = (1ULL << MSR_SF) | (1ULL << MSR_ME); + env->nip = start; + env->gpr[3] = r3; + env->halted = 0; + + qemu_cpu_kick(cpu); + + rtas_st(rets, 0, 0); + return; + } + + /* Didn't find a matching cpu */ + rtas_st(rets, 0, -3); +} + +static struct rtas_call { + const char *name; + spapr_rtas_fn fn; +} rtas_table[TOKEN_MAX]; + +struct rtas_call *rtas_next = rtas_table; + +target_ulong spapr_rtas_call(sPAPREnvironment *spapr, + uint32_t token, uint32_t nargs, target_ulong args, + uint32_t nret, target_ulong rets) +{ + if ((token >= TOKEN_BASE) + && ((token - TOKEN_BASE) < TOKEN_MAX)) { + struct rtas_call *call = rtas_table + (token - TOKEN_BASE); + + if (call->fn) { + call->fn(spapr, token, nargs, args, nret, rets); + return H_SUCCESS; + } + } + + /* HACK: Some Linux early debug code uses RTAS display-character, + * but assumes the token value is 0xa (which it is on some real + * machines) without looking it up in the device tree. This + * special case makes this work */ + if (token == 0xa) { + rtas_display_character(spapr, 0xa, nargs, args, nret, rets); + return H_SUCCESS; + } + + hcall_dprintf("Unknown RTAS token 0x%x\n", token); + rtas_st(rets, 0, -3); + return H_PARAMETER; +} + +int spapr_rtas_register(const char *name, spapr_rtas_fn fn) +{ + int i; + + for (i = 0; i < (rtas_next - rtas_table); i++) { + if (strcmp(name, rtas_table[i].name) == 0) { + fprintf(stderr, "RTAS call \"%s\" registered twice\n", name); + exit(1); + } + } + + assert(rtas_next < (rtas_table + TOKEN_MAX)); + + rtas_next->name = name; + rtas_next->fn = fn; + + return (rtas_next++ - rtas_table) + TOKEN_BASE; +} + +int spapr_rtas_device_tree_setup(void *fdt, hwaddr rtas_addr, + hwaddr rtas_size) +{ + int ret; + int i; + + ret = fdt_add_mem_rsv(fdt, rtas_addr, rtas_size); + if (ret < 0) { + fprintf(stderr, "Couldn't add RTAS reserve entry: %s\n", + fdt_strerror(ret)); + return ret; + } + + ret = qemu_devtree_setprop_cell(fdt, "/rtas", "linux,rtas-base", + rtas_addr); + if (ret < 0) { + fprintf(stderr, "Couldn't add linux,rtas-base property: %s\n", + fdt_strerror(ret)); + return ret; + } + + ret = qemu_devtree_setprop_cell(fdt, "/rtas", "linux,rtas-entry", + rtas_addr); + if (ret < 0) { + fprintf(stderr, "Couldn't add linux,rtas-entry property: %s\n", + fdt_strerror(ret)); + return ret; + } + + ret = qemu_devtree_setprop_cell(fdt, "/rtas", "rtas-size", + rtas_size); + if (ret < 0) { + fprintf(stderr, "Couldn't add rtas-size property: %s\n", + fdt_strerror(ret)); + return ret; + } + + for (i = 0; i < TOKEN_MAX; i++) { + struct rtas_call *call = &rtas_table[i]; + + if (!call->name) { + continue; + } + + ret = qemu_devtree_setprop_cell(fdt, "/rtas", call->name, + i + TOKEN_BASE); + if (ret < 0) { + fprintf(stderr, "Couldn't add rtas token for %s: %s\n", + call->name, fdt_strerror(ret)); + return ret; + } + + } + return 0; +} + +static void core_rtas_register_types(void) +{ + spapr_rtas_register("display-character", rtas_display_character); + spapr_rtas_register("get-time-of-day", rtas_get_time_of_day); + spapr_rtas_register("set-time-of-day", rtas_set_time_of_day); + spapr_rtas_register("power-off", rtas_power_off); + spapr_rtas_register("system-reboot", rtas_system_reboot); + spapr_rtas_register("query-cpu-stopped-state", + rtas_query_cpu_stopped_state); + spapr_rtas_register("start-cpu", rtas_start_cpu); +} + +type_init(core_rtas_register_types) |