From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50053) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fL5VO-0000lv-GX for qemu-devel@nongnu.org; Tue, 22 May 2018 07:31:05 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1fL5VL-0000lQ-9R for qemu-devel@nongnu.org; Tue, 22 May 2018 07:31:02 -0400 References: <20180521140402.23318-1-peter.maydell@linaro.org> <20180521140402.23318-19-peter.maydell@linaro.org> From: Auger Eric Message-ID: <5c004b51-6d28-58e8-2674-0df659c51e65@redhat.com> Date: Tue, 22 May 2018 13:30:44 +0200 MIME-Version: 1.0 In-Reply-To: <20180521140402.23318-19-peter.maydell@linaro.org> Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: 7bit Subject: Re: [Qemu-devel] [PATCH 18/27] hw/misc/tz-mpc.c: Implement the Arm TrustZone Memory Protection Controller List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Peter Maydell , qemu-arm@nongnu.org, qemu-devel@nongnu.org Cc: Paolo Bonzini , Richard Henderson , =?UTF-8?Q?Alex_Benn=c3=a9e?= , patches@linaro.org Hi Peter, On 05/21/2018 04:03 PM, Peter Maydell wrote: > Implement the Arm TrustZone Memory Protection Controller, which sits > in front of RAM and allows secure software to configure it to either > pass through or reject transactions. > > We implement the MPC as a QEMU IOMMU, which will direct transactions > either through to the devices and memory behind it or to a special > "never works" AddressSpace if they are blocked. > > This initial commit implements the skeleton of the device: > * it always permits accesses > * it doesn't implement most of the registers > * it doesn't implement the interrupt or other behaviour > for blocked transactions > > Signed-off-by: Peter Maydell this patch does not seem to apply on master. Thanks Eric > --- > hw/misc/Makefile.objs | 1 + > include/hw/misc/tz-mpc.h | 70 ++++++ > hw/misc/tz-mpc.c | 381 ++++++++++++++++++++++++++++++++ > MAINTAINERS | 2 + > default-configs/arm-softmmu.mak | 1 + > hw/misc/trace-events | 7 + > 6 files changed, 462 insertions(+) > create mode 100644 include/hw/misc/tz-mpc.h > create mode 100644 hw/misc/tz-mpc.c > > diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs > index 00e834d0f0..7295e676a6 100644 > --- a/hw/misc/Makefile.objs > +++ b/hw/misc/Makefile.objs > @@ -61,6 +61,7 @@ obj-$(CONFIG_MIPS_ITU) += mips_itu.o > obj-$(CONFIG_MPS2_FPGAIO) += mps2-fpgaio.o > obj-$(CONFIG_MPS2_SCC) += mps2-scc.o > > +obj-$(CONFIG_TZ_MPC) += tz-mpc.o > obj-$(CONFIG_TZ_PPC) += tz-ppc.o > obj-$(CONFIG_IOTKIT_SECCTL) += iotkit-secctl.o > > diff --git a/include/hw/misc/tz-mpc.h b/include/hw/misc/tz-mpc.h > new file mode 100644 > index 0000000000..b5eaf1699e > --- /dev/null > +++ b/include/hw/misc/tz-mpc.h > @@ -0,0 +1,70 @@ > +/* > + * ARM TrustZone memory protection controller emulation > + * > + * Copyright (c) 2018 Linaro Limited > + * Written by Peter Maydell > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 or > + * (at your option) any later version. > + */ > + > +/* This is a model of the TrustZone memory protection controller (MPC). > + * It is documented in the ARM CoreLink SIE-200 System IP for Embedded TRM > + * (DDI 0571G): > + * https://developer.arm.com/products/architecture/m-profile/docs/ddi0571/g > + * > + * The MPC sits in front of memory and allows secure software to > + * configure it to either pass through or reject transactions. > + * Rejected transactions may be configured to either be aborted, or to > + * behave as RAZ/WI. An interrupt can be signalled for a rejected transaction. > + * > + * The MPC has a register interface which the guest uses to configure it. > + * > + * QEMU interface: > + * + sysbus MMIO region 0: MemoryRegion for the MPC's config registers > + * + sysbus MMIO region 1: MemoryRegion for the upstream end of the MPC > + * + Property "downstream": MemoryRegion defining the downstream memory > + * + Named GPIO output "irq": set for a transaction-failed interrupt > + */ > + > +#ifndef TZ_MPC_H > +#define TZ_MPC_H > + > +#include "hw/sysbus.h" > + > +#define TYPE_TZ_MPC "tz-mpc" > +#define TZ_MPC(obj) OBJECT_CHECK(TZMPC, (obj), TYPE_TZ_MPC) > + > +#define TZ_NUM_PORTS 16 > + > +#define TYPE_TZ_MPC_IOMMU_MEMORY_REGION "tz-mpc-iommu-memory-region" > + > +typedef struct TZMPC TZMPC; > + > +struct TZMPC { > + /*< private >*/ > + SysBusDevice parent_obj; > + > + /*< public >*/ > + > + qemu_irq irq; > + > + /* Properties */ > + MemoryRegion *downstream; > + > + hwaddr blocksize; > + uint32_t blk_max; > + > + /* MemoryRegions exposed to user */ > + MemoryRegion regmr; > + IOMMUMemoryRegion upstream; > + > + /* MemoryRegion used internally */ > + MemoryRegion blocked_io; > + > + AddressSpace downstream_as; > + AddressSpace blocked_io_as; > +}; > + > +#endif > diff --git a/hw/misc/tz-mpc.c b/hw/misc/tz-mpc.c > new file mode 100644 > index 0000000000..d4467ccc3b > --- /dev/null > +++ b/hw/misc/tz-mpc.c > @@ -0,0 +1,381 @@ > +/* > + * ARM TrustZone memory protection controller emulation > + * > + * Copyright (c) 2018 Linaro Limited > + * Written by Peter Maydell > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 or > + * (at your option) any later version. > + */ > + > +#include "qemu/osdep.h" > +#include "qemu/log.h" > +#include "qapi/error.h" > +#include "trace.h" > +#include "hw/sysbus.h" > +#include "hw/registerfields.h" > +#include "hw/misc/tz-mpc.h" > + > +/* Our IOMMU has two IOMMU indexes, one for secure transactions and one for > + * non-secure transactions. > + */ > +enum { > + IOMMU_IDX_S, > + IOMMU_IDX_NS, > + IOMMU_NUM_INDEXES, > +}; > + > +/* Config registers */ > +REG32(CTRL, 0x00) > +REG32(BLK_MAX, 0x10) > +REG32(BLK_CFG, 0x14) > +REG32(BLK_IDX, 0x18) > +REG32(BLK_LUT, 0x1c) > +REG32(INT_STAT, 0x20) > +REG32(INT_CLEAR, 0x24) > +REG32(INT_EN, 0x28) > +REG32(INT_INFO1, 0x2c) > +REG32(INT_INFO2, 0x30) > +REG32(INT_SET, 0x34) > +REG32(PIDR4, 0xfd0) > +REG32(PIDR5, 0xfd4) > +REG32(PIDR6, 0xfd8) > +REG32(PIDR7, 0xfdc) > +REG32(PIDR0, 0xfe0) > +REG32(PIDR1, 0xfe4) > +REG32(PIDR2, 0xfe8) > +REG32(PIDR3, 0xfec) > +REG32(CIDR0, 0xff0) > +REG32(CIDR1, 0xff4) > +REG32(CIDR2, 0xff8) > +REG32(CIDR3, 0xffc) > + > +static const uint8_t tz_mpc_idregs[] = { > + 0x04, 0x00, 0x00, 0x00, > + 0x60, 0xb8, 0x1b, 0x00, > + 0x0d, 0xf0, 0x05, 0xb1, > +}; > + > +static MemTxResult tz_mpc_reg_read(void *opaque, hwaddr addr, > + uint64_t *pdata, > + unsigned size, MemTxAttrs attrs) > +{ > + uint64_t r; > + uint32_t offset = addr & ~0x3; > + > + switch (offset) { > + case A_PIDR4: > + case A_PIDR5: > + case A_PIDR6: > + case A_PIDR7: > + case A_PIDR0: > + case A_PIDR1: > + case A_PIDR2: > + case A_PIDR3: > + case A_CIDR0: > + case A_CIDR1: > + case A_CIDR2: > + case A_CIDR3: > + r = tz_mpc_idregs[(offset - A_PIDR4) / 4]; > + break; > + case A_INT_CLEAR: > + case A_INT_SET: > + qemu_log_mask(LOG_GUEST_ERROR, > + "TZ MPC register read: write-only offset 0x%x\n", > + offset); > + r = 0; > + break; > + default: > + qemu_log_mask(LOG_GUEST_ERROR, > + "TZ MPC register read: bad offset 0x%x\n", offset); > + r = 0; > + break; > + } > + > + if (size != 4) { > + /* None of our registers are read-sensitive (except BLK_LUT, > + * which can special case the "size not 4" case), so just > + * pull the right bytes out of the word read result. > + */ > + r = extract32(r, (addr & 3) * 8, size * 8); > + } > + > + trace_tz_mpc_reg_read(addr, r, size); > + *pdata = r; > + return MEMTX_OK; > +} > + > +static MemTxResult tz_mpc_reg_write(void *opaque, hwaddr addr, > + uint64_t value, > + unsigned size, MemTxAttrs attrs) > +{ > + uint32_t offset = addr & ~0x3; > + > + trace_tz_mpc_reg_write(addr, value, size); > + > + if (size != 4) { > + /* Expand the byte or halfword write to a full word size. > + * In most cases we can do this with zeroes; the exceptions > + * are CTRL, BLK_IDX and BLK_LUT. > + */ > + uint32_t oldval; > + > + switch (offset) { > + /* As we add support for registers which need expansions > + * other than zeroes we'll fill in cases here. > + */ > + default: > + oldval = 0; > + break; > + } > + value = deposit32(oldval, (addr & 3) * 8, size * 8, value); > + } > + > + switch (offset) { > + case A_PIDR4: > + case A_PIDR5: > + case A_PIDR6: > + case A_PIDR7: > + case A_PIDR0: > + case A_PIDR1: > + case A_PIDR2: > + case A_PIDR3: > + case A_CIDR0: > + case A_CIDR1: > + case A_CIDR2: > + case A_CIDR3: > + qemu_log_mask(LOG_GUEST_ERROR, > + "TZ MPC register write: read-only offset 0x%x\n", offset); > + break; > + default: > + qemu_log_mask(LOG_GUEST_ERROR, > + "TZ MPC register write: bad offset 0x%x\n", offset); > + break; > + } > + > + return MEMTX_OK; > +} > + > +static const MemoryRegionOps tz_mpc_reg_ops = { > + .read_with_attrs = tz_mpc_reg_read, > + .write_with_attrs = tz_mpc_reg_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid.min_access_size = 1, > + .valid.max_access_size = 4, > + .impl.min_access_size = 1, > + .impl.max_access_size = 4, > +}; > + > +/* Accesses only reach these read and write functions if the MPC is > + * blocking them; non-blocked accesses go directly to the downstream > + * memory region without passing through this code. > + */ > +static MemTxResult tz_mpc_mem_blocked_read(void *opaque, hwaddr addr, > + uint64_t *pdata, > + unsigned size, MemTxAttrs attrs) > +{ > + trace_tz_mpc_mem_blocked_read(addr, size, attrs.secure); > + > + *pdata = 0; > + return MEMTX_OK; > +} > + > +static MemTxResult tz_mpc_mem_blocked_write(void *opaque, hwaddr addr, > + uint64_t value, > + unsigned size, MemTxAttrs attrs) > +{ > + trace_tz_mpc_mem_blocked_write(addr, value, size, attrs.secure); > + > + return MEMTX_OK; > +} > + > +static const MemoryRegionOps tz_mpc_mem_blocked_ops = { > + .read_with_attrs = tz_mpc_mem_blocked_read, > + .write_with_attrs = tz_mpc_mem_blocked_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid.min_access_size = 1, > + .valid.max_access_size = 8, > + .impl.min_access_size = 1, > + .impl.max_access_size = 8, > +}; > + > +static IOMMUTLBEntry tz_mpc_translate(IOMMUMemoryRegion *iommu, > + hwaddr addr, IOMMUAccessFlags flags, > + int iommu_idx) > +{ > + TZMPC *s = TZ_MPC(container_of(iommu, TZMPC, upstream)); > + bool ok; > + > + IOMMUTLBEntry ret = { > + .iova = addr & ~(s->blocksize - 1), > + .translated_addr = addr & ~(s->blocksize - 1), > + .addr_mask = s->blocksize - 1, > + .perm = IOMMU_RW, > + }; > + > + /* Look at the per-block configuration for this address, and > + * return a TLB entry directing the transaction at either > + * downstream_as or blocked_io_as, as appropriate. > + * For the moment, always permit accesses. > + */ > + ok = true; > + > + trace_tz_mpc_translate(addr, flags, > + iommu_idx == IOMMU_IDX_S ? "S" : "NS", > + ok ? "pass" : "block"); > + > + ret.target_as = ok ? &s->downstream_as : &s->blocked_io_as; > + return ret; > +} > + > +static int tz_mpc_attrs_to_index(IOMMUMemoryRegion *iommu, MemTxAttrs attrs) > +{ > + /* We treat unspecified attributes like secure. Transactions with > + * unspecified attributes come from places like > + * cpu_physical_memory_write_rom() for initial image load, and we want > + * those to pass through the from-reset "everything is secure" config. > + * All the real during-emulation transactions from the CPU will > + * specify attributes. > + */ > + return (attrs.unspecified || attrs.secure) ? IOMMU_IDX_S : IOMMU_IDX_NS; > +} > + > +static int tz_mpc_num_indexes(IOMMUMemoryRegion *iommu) > +{ > + return IOMMU_NUM_INDEXES; > +} > + > +static void tz_mpc_reset(DeviceState *dev) > +{ > +} > + > +static void tz_mpc_init(Object *obj) > +{ > + DeviceState *dev = DEVICE(obj); > + TZMPC *s = TZ_MPC(obj); > + > + qdev_init_gpio_out_named(dev, &s->irq, "irq", 1); > +} > + > +static void tz_mpc_realize(DeviceState *dev, Error **errp) > +{ > + Object *obj = OBJECT(dev); > + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); > + TZMPC *s = TZ_MPC(dev); > + uint64_t size; > + > + /* We can't create the upstream end of the port until realize, > + * as we don't know the size of the MR used as the downstream until then. > + * We insist on having a downstream, to avoid complicating the code > + * with handling the "don't know how big this is" case. It's easy > + * enough for the user to create an unimplemented_device as downstream > + * if they have nothing else to plug into this. > + */ > + if (!s->downstream) { > + error_setg(errp, "MPC 'downstream' link not set"); > + return; > + } > + > + size = memory_region_size(s->downstream); > + > + memory_region_init_iommu(&s->upstream, sizeof(s->upstream), > + TYPE_TZ_MPC_IOMMU_MEMORY_REGION, > + obj, "tz-mpc-upstream", size); > + > + /* In real hardware the block size is configurable. In QEMU we could > + * make it configurable but will need it to be at least as big as the > + * target page size so we can execute out of the resulting MRs. Guest > + * software is supposed to check the block size using the BLK_CFG > + * register, so make it fixed at the page size. > + */ > + s->blocksize = memory_region_iommu_get_min_page_size(&s->upstream); > + if (size % s->blocksize != 0) { > + error_setg(errp, > + "MPC 'downstream' size %" PRId64 > + " is not a multiple of %" HWADDR_PRIx " bytes", > + size, s->blocksize); > + object_unref(OBJECT(&s->upstream)); > + return; > + } > + > + /* BLK_MAX is the max value of BLK_IDX, which indexes an array of 32-bit > + * words, each bit of which indicates one block. > + */ > + s->blk_max = DIV_ROUND_UP(size / s->blocksize, 32); > + > + memory_region_init_io(&s->regmr, obj, &tz_mpc_reg_ops, > + s, "tz-mpc-regs", 0x1000); > + sysbus_init_mmio(sbd, &s->regmr); > + > + sysbus_init_mmio(sbd, MEMORY_REGION(&s->upstream)); > + > + /* This memory region is not exposed to users of this device as a > + * sysbus MMIO region, but is instead used internally as something > + * that our IOMMU translate function might direct accesses to. > + */ > + memory_region_init_io(&s->blocked_io, obj, &tz_mpc_mem_blocked_ops, > + s, "tz-mpc-blocked-io", size); > + > + address_space_init(&s->downstream_as, s->downstream, > + "tz-mpc-downstream"); > + address_space_init(&s->blocked_io_as, &s->blocked_io, > + "tz-mpc-blocked-io"); > +} > + > +static const VMStateDescription tz_mpc_vmstate = { > + .name = "tz-mpc", > + .version_id = 1, > + .minimum_version_id = 1, > + .fields = (VMStateField[]) { > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static Property tz_mpc_properties[] = { > + DEFINE_PROP_LINK("downstream", TZMPC, downstream, > + TYPE_MEMORY_REGION, MemoryRegion *), > + DEFINE_PROP_END_OF_LIST(), > +}; > + > +static void tz_mpc_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + > + dc->realize = tz_mpc_realize; > + dc->vmsd = &tz_mpc_vmstate; > + dc->reset = tz_mpc_reset; > + dc->props = tz_mpc_properties; > +} > + > +static const TypeInfo tz_mpc_info = { > + .name = TYPE_TZ_MPC, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(TZMPC), > + .instance_init = tz_mpc_init, > + .class_init = tz_mpc_class_init, > +}; > + > +static void tz_mpc_iommu_memory_region_class_init(ObjectClass *klass, > + void *data) > +{ > + IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass); > + > + imrc->translate = tz_mpc_translate; > + imrc->attrs_to_index = tz_mpc_attrs_to_index; > + imrc->num_indexes = tz_mpc_num_indexes; > +} > + > +static const TypeInfo tz_mpc_iommu_memory_region_info = { > + .name = TYPE_TZ_MPC_IOMMU_MEMORY_REGION, > + .parent = TYPE_IOMMU_MEMORY_REGION, > + .class_init = tz_mpc_iommu_memory_region_class_init, > +}; > + > +static void tz_mpc_register_types(void) > +{ > + type_register_static(&tz_mpc_info); > + type_register_static(&tz_mpc_iommu_memory_region_info); > +} > + > +type_init(tz_mpc_register_types); > diff --git a/MAINTAINERS b/MAINTAINERS > index 1823f900b9..9cddb699df 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -449,6 +449,8 @@ F: hw/char/cmsdk-apb-uart.c > F: include/hw/char/cmsdk-apb-uart.h > F: hw/misc/tz-ppc.c > F: include/hw/misc/tz-ppc.h > +F: hw/misc/tz-mpc.c > +F: include/hw/misc/tz-mpc.h > > ARM cores > M: Peter Maydell > diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak > index dd29e741c2..30e73847ac 100644 > --- a/default-configs/arm-softmmu.mak > +++ b/default-configs/arm-softmmu.mak > @@ -106,6 +106,7 @@ CONFIG_CMSDK_APB_UART=y > CONFIG_MPS2_FPGAIO=y > CONFIG_MPS2_SCC=y > > +CONFIG_TZ_MPC=y > CONFIG_TZ_PPC=y > CONFIG_IOTKIT=y > CONFIG_IOTKIT_SECCTL=y > diff --git a/hw/misc/trace-events b/hw/misc/trace-events > index 562d9ed005..d4835c8970 100644 > --- a/hw/misc/trace-events > +++ b/hw/misc/trace-events > @@ -84,6 +84,13 @@ mos6522_set_sr_int(void) "set sr_int" > mos6522_write(uint64_t addr, uint64_t val) "reg=0x%"PRIx64 " val=0x%"PRIx64 > mos6522_read(uint64_t addr, unsigned val) "reg=0x%"PRIx64 " val=0x%x" > > +# hw/misc/tz-mpc.c > +tz_mpc_reg_read(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs read: offset 0x%x data 0x%" PRIx64 " size %u" > +tz_mpc_reg_write(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs write: offset 0x%x data 0x%" PRIx64 " size %u" > +tz_mpc_mem_blocked_read(uint64_t addr, unsigned size, bool secure) "TZ MPC blocked read: offset 0x%" PRIx64 " size %u secure %d" > +tz_mpc_mem_blocked_write(uint64_t addr, uint64_t data, unsigned size, bool secure) "TZ MPC blocked write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u secure %d" > +tz_mpc_translate(uint64_t addr, int flags, const char *idx, const char *res) "TZ MPC translate: addr 0x%" PRIx64 " flags 0x%x iommu_idx %s: %s" > + > # hw/misc/tz-ppc.c > tz_ppc_reset(void) "TZ PPC: reset" > tz_ppc_cfg_nonsec(int n, int level) "TZ PPC: cfg_nonsec[%d] = %d" >