From d317091d5ed616328ef5a7b8502167c4e7de8e6b Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 4 Jul 2016 13:06:35 +0100 Subject: [PATCH 01/23] linux-user: Make semihosting heap/stack fields abi_ulongs The fields in the TaskState heap_base, heap_limit and stack_base are all guest addresses (representing the locations of the heap and stack for the guest binary), so they should be abi_ulong rather than uint32_t. (This only in practice affects ARM AArch64 since all the other semihosting implementations are 32-bit.) Signed-off-by: Peter Maydell Reviewed-by: Laurent Desnogues Message-id: 1466783381-29506-2-git-send-email-peter.maydell@linaro.org --- linux-user/qemu.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index e8a5aede95..cdf23a723a 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -116,10 +116,10 @@ typedef struct TaskState { #endif #if defined(TARGET_ARM) || defined(TARGET_M68K) || defined(TARGET_UNICORE32) /* Extra fields for semihosted binaries. */ - uint32_t heap_base; - uint32_t heap_limit; + abi_ulong heap_base; + abi_ulong heap_limit; #endif - uint32_t stack_base; + abi_ulong stack_base; int used; /* non zero if used */ struct image_info *info; struct linux_binprm *bprm; From f5666418c449b49917ea24cdb0bed76a602a3c74 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 4 Jul 2016 13:06:35 +0100 Subject: [PATCH 02/23] target-arm/arm-semi.c: Fix SYS_HEAPINFO for 64-bit guests SYS_HEAPINFO is one of the few semihosting calls which has to write values back into a parameter block in memory. When we added support for 64-bit semihosting we updated the code which reads from the parameter block to read 64-bit words but forgot to change the code that writes back into the block. Update it to treat the block as a set of words of the appropriate width for the guest. Signed-off-by: Peter Maydell Message-id: 1466783381-29506-3-git-send-email-peter.maydell@linaro.org --- target-arm/arm-semi.c | 47 ++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/target-arm/arm-semi.c b/target-arm/arm-semi.c index 8be0645eb0..d50726f65d 100644 --- a/target-arm/arm-semi.c +++ b/target-arm/arm-semi.c @@ -564,8 +564,10 @@ target_ulong do_arm_semihosting(CPUARMState *env) } case TARGET_SYS_HEAPINFO: { - uint32_t *ptr; + target_ulong retvals[4]; uint32_t limit; + int i; + GET_ARG(0); #ifdef CONFIG_USER_ONLY @@ -587,30 +589,33 @@ target_ulong do_arm_semihosting(CPUARMState *env) ts->heap_limit = limit; } - ptr = lock_user(VERIFY_WRITE, arg0, 16, 0); - if (!ptr) { - /* FIXME - should this error code be -TARGET_EFAULT ? */ - return (uint32_t)-1; - } - ptr[0] = tswap32(ts->heap_base); - ptr[1] = tswap32(ts->heap_limit); - ptr[2] = tswap32(ts->stack_base); - ptr[3] = tswap32(0); /* Stack limit. */ - unlock_user(ptr, arg0, 16); + retvals[0] = ts->heap_base; + retvals[1] = ts->heap_limit; + retvals[2] = ts->stack_base; + retvals[3] = 0; /* Stack limit. */ #else limit = ram_size; - ptr = lock_user(VERIFY_WRITE, arg0, 16, 0); - if (!ptr) { - /* FIXME - should this error code be -TARGET_EFAULT ? */ - return (uint32_t)-1; - } /* TODO: Make this use the limit of the loaded application. */ - ptr[0] = tswap32(limit / 2); - ptr[1] = tswap32(limit); - ptr[2] = tswap32(limit); /* Stack base */ - ptr[3] = tswap32(0); /* Stack limit. */ - unlock_user(ptr, arg0, 16); + retvals[0] = limit / 2; + retvals[1] = limit; + retvals[2] = limit; /* Stack base */ + retvals[3] = 0; /* Stack limit. */ #endif + + for (i = 0; i < ARRAY_SIZE(retvals); i++) { + bool fail; + + if (is_a64(env)) { + fail = put_user_u64(retvals[i], arg0 + i * 8); + } else { + fail = put_user_u32(retvals[i], arg0 + i * 4); + } + + if (fail) { + /* Couldn't write back to argument block */ + return -1; + } + } return 0; } case TARGET_SYS_EXIT: From a1777f7f6462c66e1ee6e98f0d5c431bfe988aa5 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 4 Jul 2016 13:06:35 +0100 Subject: [PATCH 03/23] memory: Provide memory_region_init_rom() Provide a new helper function memory_region_init_rom() for memory regions which are read-only (and unlike those created by memory_region_init_rom_device() don't have special behaviour for writes). This has the same behaviour as calling memory_region_init_ram() and then memory_region_set_readonly() (which is what we do today in boards with pure ROMs) but is a more easily discoverable API for the purpose. Signed-off-by: Peter Maydell Message-id: 1467122287-24974-2-git-send-email-peter.maydell@linaro.org --- docs/memory.txt | 9 +++++++-- include/exec/memory.h | 19 +++++++++++++++++++ memory.c | 15 +++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/memory.txt b/docs/memory.txt index 431d9ca88f..811b1bd3c5 100644 --- a/docs/memory.txt +++ b/docs/memory.txt @@ -41,8 +41,13 @@ MemoryRegion): MemoryRegionOps structure describing the callbacks. - ROM: a ROM memory region works like RAM for reads (directly accessing - a region of host memory), but like MMIO for writes (invoking a callback). - You initialize these with memory_region_init_rom_device(). + a region of host memory), and forbids writes. You initialize these with + memory_region_init_rom(). + +- ROM device: a ROM device memory region works like RAM for reads + (directly accessing a region of host memory), but like MMIO for + writes (invoking a callback). You initialize these with + memory_region_init_rom_device(). - IOMMU region: an IOMMU region translates addresses of accesses made to it and forwards them to some other target memory region. As the name suggests, diff --git a/include/exec/memory.h b/include/exec/memory.h index 23c7399131..2d9ea3c088 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -444,6 +444,25 @@ void memory_region_init_alias(MemoryRegion *mr, hwaddr offset, uint64_t size); +/** + * memory_region_init_rom: Initialize a ROM memory region. + * + * This has the same effect as calling memory_region_init_ram() + * and then marking the resulting region read-only with + * memory_region_set_readonly(). + * + * @mr: the #MemoryRegion to be initialized. + * @owner: the object that tracks the region's reference count + * @name: the name of the region. + * @size: size of the region. + * @errp: pointer to Error*, to store an error if it happens. + */ +void memory_region_init_rom(MemoryRegion *mr, + struct Object *owner, + const char *name, + uint64_t size, + Error **errp); + /** * memory_region_init_rom_device: Initialize a ROM memory region. Writes are * handled via callbacks. diff --git a/memory.c b/memory.c index 33799e810b..ecb565ea81 100644 --- a/memory.c +++ b/memory.c @@ -1376,6 +1376,21 @@ void memory_region_init_alias(MemoryRegion *mr, mr->alias_offset = offset; } +void memory_region_init_rom(MemoryRegion *mr, + struct Object *owner, + const char *name, + uint64_t size, + Error **errp) +{ + memory_region_init(mr, owner, name, size); + mr->ram = true; + mr->readonly = true; + mr->terminates = true; + mr->destructor = memory_region_destructor_ram; + mr->ram_block = qemu_ram_alloc(size, mr, errp); + mr->dirty_log_mask = tcg_enabled() ? (1 << DIRTY_MEMORY_CODE) : 0; +} + void memory_region_init_rom_device(MemoryRegion *mr, Object *owner, const MemoryRegionOps *ops, From a7aeb5f7b2c713e2ab7e0a142e0c89f7b2aa5bb7 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 4 Jul 2016 13:06:35 +0100 Subject: [PATCH 04/23] imx: Use memory_region_init_rom() for ROMs The imx boards were all incorrectly creating ROMs using memory_region_init_rom_device() with a NULL ops pointer. This will cause QEMU to abort if the guest tries to write to the ROM. Switch to the new memory_region_init_rom() instead. Signed-off-by: Peter Maydell Message-id: 1467122287-24974-3-git-send-email-peter.maydell@linaro.org --- hw/arm/fsl-imx25.c | 8 ++++---- hw/arm/fsl-imx31.c | 9 ++++----- hw/arm/fsl-imx6.c | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/hw/arm/fsl-imx25.c b/hw/arm/fsl-imx25.c index 1cd749aa4b..1a53e51cf1 100644 --- a/hw/arm/fsl-imx25.c +++ b/hw/arm/fsl-imx25.c @@ -249,16 +249,16 @@ static void fsl_imx25_realize(DeviceState *dev, Error **errp) } /* initialize 2 x 16 KB ROM */ - memory_region_init_rom_device(&s->rom[0], NULL, NULL, NULL, - "imx25.rom0", FSL_IMX25_ROM0_SIZE, &err); + memory_region_init_rom(&s->rom[0], NULL, + "imx25.rom0", FSL_IMX25_ROM0_SIZE, &err); if (err) { error_propagate(errp, err); return; } memory_region_add_subregion(get_system_memory(), FSL_IMX25_ROM0_ADDR, &s->rom[0]); - memory_region_init_rom_device(&s->rom[1], NULL, NULL, NULL, - "imx25.rom1", FSL_IMX25_ROM1_SIZE, &err); + memory_region_init_rom(&s->rom[1], NULL, + "imx25.rom1", FSL_IMX25_ROM1_SIZE, &err); if (err) { error_propagate(errp, err); return; diff --git a/hw/arm/fsl-imx31.c b/hw/arm/fsl-imx31.c index 31a3a87911..b283b71eb4 100644 --- a/hw/arm/fsl-imx31.c +++ b/hw/arm/fsl-imx31.c @@ -219,9 +219,8 @@ static void fsl_imx31_realize(DeviceState *dev, Error **errp) } /* On a real system, the first 16k is a `secure boot rom' */ - memory_region_init_rom_device(&s->secure_rom, NULL, NULL, NULL, - "imx31.secure_rom", - FSL_IMX31_SECURE_ROM_SIZE, &err); + memory_region_init_rom(&s->secure_rom, NULL, "imx31.secure_rom", + FSL_IMX31_SECURE_ROM_SIZE, &err); if (err) { error_propagate(errp, err); return; @@ -230,8 +229,8 @@ static void fsl_imx31_realize(DeviceState *dev, Error **errp) &s->secure_rom); /* There is also a 16k ROM */ - memory_region_init_rom_device(&s->rom, NULL, NULL, NULL, "imx31.rom", - FSL_IMX31_ROM_SIZE, &err); + memory_region_init_rom(&s->rom, NULL, "imx31.rom", + FSL_IMX31_ROM_SIZE, &err); if (err) { error_propagate(errp, err); return; diff --git a/hw/arm/fsl-imx6.c b/hw/arm/fsl-imx6.c index 0c00e7a560..ed392a9e7f 100644 --- a/hw/arm/fsl-imx6.c +++ b/hw/arm/fsl-imx6.c @@ -399,8 +399,8 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) FSL_IMX6_ENET_MAC_1588_IRQ)); /* ROM memory */ - memory_region_init_rom_device(&s->rom, NULL, NULL, NULL, "imx6.rom", - FSL_IMX6_ROM_SIZE, &err); + memory_region_init_rom(&s->rom, NULL, "imx6.rom", + FSL_IMX6_ROM_SIZE, &err); if (err) { error_propagate(errp, err); return; @@ -409,8 +409,8 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) &s->rom); /* CAAM memory */ - memory_region_init_rom_device(&s->caam, NULL, NULL, NULL, "imx6.caam", - FSL_IMX6_CAAM_MEM_SIZE, &err); + memory_region_init_rom(&s->caam, NULL, "imx6.caam", + FSL_IMX6_CAAM_MEM_SIZE, &err); if (err) { error_propagate(errp, err); return; From 39e0b03dec518254fabd2acff29548d3f1d2b754 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 4 Jul 2016 13:06:35 +0100 Subject: [PATCH 05/23] memory: Assert that memory_region_init_rom_device() ops aren't NULL It doesn't make sense to pass a NULL ops argument to memory_region_init_rom_device(), because the effect will be that if the guest tries to write to the memory region then QEMU will segfault. Catch the bug earlier by sanity checking the arguments to this function, and remove the misleading documentation that suggests that passing NULL might be sensible. Signed-off-by: Peter Maydell Message-id: 1467122287-24974-4-git-send-email-peter.maydell@linaro.org --- include/exec/memory.h | 5 +---- memory.c | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/exec/memory.h b/include/exec/memory.h index 2d9ea3c088..3e4d4164cd 100644 --- a/include/exec/memory.h +++ b/include/exec/memory.h @@ -467,12 +467,9 @@ void memory_region_init_rom(MemoryRegion *mr, * memory_region_init_rom_device: Initialize a ROM memory region. Writes are * handled via callbacks. * - * If NULL callbacks pointer is given, then I/O space is not supposed to be - * handled by QEMU itself. Any access via the memory API will cause an abort(). - * * @mr: the #MemoryRegion to be initialized. * @owner: the object that tracks the region's reference count - * @ops: callbacks for write access handling. + * @ops: callbacks for write access handling (must not be NULL). * @name: the name of the region. * @size: size of the region. * @errp: pointer to Error*, to store an error if it happens. diff --git a/memory.c b/memory.c index ecb565ea81..0eb6895fe6 100644 --- a/memory.c +++ b/memory.c @@ -1399,6 +1399,7 @@ void memory_region_init_rom_device(MemoryRegion *mr, uint64_t size, Error **errp) { + assert(ops); memory_region_init(mr, owner, name, size); mr->ops = ops; mr->opaque = opaque; From a19861666b574f54c71d1fea9a8d8a84915dfa70 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 4 Jul 2016 13:06:35 +0100 Subject: [PATCH 06/23] armv7m_nvic: Use qemu_get_cpu(0) instead of current_cpu Starting QEMU with -S results in current_cpu containing its initial value of NULL. It is however possible to connect to such QEMU instance and query various CPU registers, one example being CPUID, and doing that results in QEMU segfaulting. Using qemu_get_cpu(0) seem reasonable enough given that ARMv7M architecture is a single core architecture. Signed-off-by: Andrey Smirnov Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/intc/armv7m_nvic.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c index 890d5d7442..06d8db6bd6 100644 --- a/hw/intc/armv7m_nvic.c +++ b/hw/intc/armv7m_nvic.c @@ -187,11 +187,11 @@ static uint32_t nvic_readl(nvic_state *s, uint32_t offset) case 0x1c: /* SysTick Calibration Value. */ return 10000; case 0xd00: /* CPUID Base. */ - cpu = ARM_CPU(current_cpu); + cpu = ARM_CPU(qemu_get_cpu(0)); return cpu->midr; case 0xd04: /* Interrupt Control State. */ /* VECTACTIVE */ - cpu = ARM_CPU(current_cpu); + cpu = ARM_CPU(qemu_get_cpu(0)); val = cpu->env.v7m.exception; if (val == 1023) { val = 0; @@ -222,7 +222,7 @@ static uint32_t nvic_readl(nvic_state *s, uint32_t offset) val |= (1 << 31); return val; case 0xd08: /* Vector Table Offset. */ - cpu = ARM_CPU(current_cpu); + cpu = ARM_CPU(qemu_get_cpu(0)); return cpu->env.v7m.vecbase; case 0xd0c: /* Application Interrupt/Reset Control. */ return 0xfa050000; @@ -349,7 +349,7 @@ static void nvic_writel(nvic_state *s, uint32_t offset, uint32_t value) } break; case 0xd08: /* Vector Table Offset. */ - cpu = ARM_CPU(current_cpu); + cpu = ARM_CPU(qemu_get_cpu(0)); cpu->env.v7m.vecbase = value & 0xffffff80; break; case 0xd0c: /* Application Interrupt/Reset Control. */ From 5d636e21c44ecf982a22a7bc4ca89186079ac283 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Mon, 4 Jul 2016 13:06:36 +0100 Subject: [PATCH 07/23] hw/arm/virt: mark the PCIe host controller as DMA coherent in the DT Since QEMU performs cacheable accesses to guest memory when doing DMA as part of the implementation of emulated PCI devices, guest drivers should use cacheable accesses as well when running under KVM. Since this essentially means that emulated PCI devices are DMA coherent, set the 'dma-coherent' DT property on the PCIe host controller DT node. This brings the DT description into line with the ACPI description, which already marks the PCI bridge as cache coherent (see commit bc64b96c984abf). Signed-off-by: Ard Biesheuvel Message-id: 1467134090-5099-1-git-send-email-ard.biesheuvel@linaro.org Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/arm/virt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index c5c125e920..6e098afd1f 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -1021,6 +1021,7 @@ static void create_pcie(const VirtBoardInfo *vbi, qemu_irq *pic, qemu_fdt_setprop_cell(vbi->fdt, nodename, "#size-cells", 2); qemu_fdt_setprop_cells(vbi->fdt, nodename, "bus-range", 0, nr_pcie_buses - 1); + qemu_fdt_setprop(vbi->fdt, nodename, "dma-coherent", NULL, 0); if (vbi->v2m_phandle) { qemu_fdt_setprop_cells(vbi->fdt, nodename, "msi-parent", From ae2923b5c20a21c6457680330506a9c13873485c Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Mon, 4 Jul 2016 13:06:36 +0100 Subject: [PATCH 08/23] bitops: Add MAKE_64BIT_MASK macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a macro that creates a 64bit value which has length number of ones shifted across by the value of shift. Signed-off-by: Alistair Francis Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Message-id: 9773244aa1c8c26b8b82cb261d8f5dd4b7b9fcf9.1467053537.git.alistair.francis@xilinx.com Signed-off-by: Peter Maydell --- include/qemu/bitops.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/qemu/bitops.h b/include/qemu/bitops.h index 15418a86df..98fb005aba 100644 --- a/include/qemu/bitops.h +++ b/include/qemu/bitops.h @@ -24,6 +24,9 @@ #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) +#define MAKE_64BIT_MASK(shift, length) \ + (((~0ULL) >> (64 - (length))) << (shift)) + /** * set_bit - Set a bit in memory * @nr: the bit to set From 1599121b57d9bcfff868ad589bb87cf6e0e2c4ac Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Mon, 4 Jul 2016 13:06:36 +0100 Subject: [PATCH 09/23] register: Add Register API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This API provides some encapsulation of registers and factors out some common functionality to common code. Bits of device state (usually MMIO registers) often have all sorts of access restrictions and semantics associated with them. This API allows you to define what those restrictions are on a bit-by-bit basis. Helper functions are then used to access the register which observe the semantics defined by the RegisterAccessInfo struct. Some features: Bits can be marked as read_only (ro field) Bits can be marked as write-1-clear (w1c field) Bits can be marked as reserved (rsvd field) Reset values can be defined (reset) Bits can be marked clear on read (cor) Pre and post action callbacks can be added to read and write ops Verbose debugging info can be enabled/disabled Useful for defining device register spaces in a data driven way. Cuts down on a lot of the verbosity and repetition in the switch-case blocks in the standard foo_mmio_read/write functions. Also useful for automated generation of device models from hardware design sources. Signed-off-by: Peter Crosthwaite Signed-off-by: Alistair Francis Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Message-id: 40d62c7e1bf6e63bb4193ec46b15092a7d981e59.1467053537.git.alistair.francis@xilinx.com Signed-off-by: Peter Maydell --- hw/core/Makefile.objs | 1 + hw/core/register.c | 160 ++++++++++++++++++++++++++++++++++++++++++ include/hw/register.h | 112 +++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 hw/core/register.c create mode 100644 include/hw/register.h diff --git a/hw/core/Makefile.objs b/hw/core/Makefile.objs index 82a9ef84f8..cfd4840397 100644 --- a/hw/core/Makefile.objs +++ b/hw/core/Makefile.objs @@ -15,4 +15,5 @@ common-obj-$(CONFIG_SOFTMMU) += machine.o common-obj-$(CONFIG_SOFTMMU) += null-machine.o common-obj-$(CONFIG_SOFTMMU) += loader.o common-obj-$(CONFIG_SOFTMMU) += qdev-properties-system.o +common-obj-$(CONFIG_SOFTMMU) += register.o common-obj-$(CONFIG_PLATFORM_BUS) += platform-bus.o diff --git a/hw/core/register.c b/hw/core/register.c new file mode 100644 index 0000000000..033b03c438 --- /dev/null +++ b/hw/core/register.c @@ -0,0 +1,160 @@ +/* + * Register Definition API + * + * Copyright (c) 2016 Xilinx Inc. + * Copyright (c) 2013 Peter Crosthwaite + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License + * for more details. + */ + +#include "qemu/osdep.h" +#include "hw/register.h" +#include "hw/qdev.h" +#include "qemu/log.h" + +static inline void register_write_val(RegisterInfo *reg, uint64_t val) +{ + g_assert(reg->data); + + switch (reg->data_size) { + case 1: + *(uint8_t *)reg->data = val; + break; + case 2: + *(uint16_t *)reg->data = val; + break; + case 4: + *(uint32_t *)reg->data = val; + break; + case 8: + *(uint64_t *)reg->data = val; + break; + default: + g_assert_not_reached(); + } +} + +static inline uint64_t register_read_val(RegisterInfo *reg) +{ + switch (reg->data_size) { + case 1: + return *(uint8_t *)reg->data; + case 2: + return *(uint16_t *)reg->data; + case 4: + return *(uint32_t *)reg->data; + case 8: + return *(uint64_t *)reg->data; + default: + g_assert_not_reached(); + } + return 0; /* unreachable */ +} + +void register_write(RegisterInfo *reg, uint64_t val, uint64_t we, + const char *prefix, bool debug) +{ + uint64_t old_val, new_val, test, no_w_mask; + const RegisterAccessInfo *ac; + + assert(reg); + + ac = reg->access; + + if (!ac || !ac->name) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: write to undefined device state " + "(written value: %#" PRIx64 ")\n", prefix, val); + return; + } + + old_val = reg->data ? register_read_val(reg) : ac->reset; + + test = (old_val ^ val) & ac->rsvd; + if (test) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: change of value in reserved bit" + "fields: %#" PRIx64 ")\n", prefix, test); + } + + test = val & ac->unimp; + if (test) { + qemu_log_mask(LOG_UNIMP, + "%s:%s writing %#" PRIx64 " to unimplemented bits:" \ + " %#" PRIx64 "", + prefix, reg->access->name, val, ac->unimp); + } + + /* Create the no write mask based on the read only, write to clear and + * reserved bit masks. + */ + no_w_mask = ac->ro | ac->w1c | ac->rsvd | ~we; + new_val = (val & ~no_w_mask) | (old_val & no_w_mask); + new_val &= ~(val & ac->w1c); + + if (ac->pre_write) { + new_val = ac->pre_write(reg, new_val); + } + + if (debug) { + qemu_log("%s:%s: write of value %#" PRIx64 "\n", prefix, ac->name, + new_val); + } + + register_write_val(reg, new_val); + + if (ac->post_write) { + ac->post_write(reg, new_val); + } +} + +uint64_t register_read(RegisterInfo *reg, uint64_t re, const char* prefix, + bool debug) +{ + uint64_t ret; + const RegisterAccessInfo *ac; + + assert(reg); + + ac = reg->access; + if (!ac || !ac->name) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: read from undefined device state\n", + prefix); + return 0; + } + + ret = reg->data ? register_read_val(reg) : ac->reset; + + register_write_val(reg, ret & ~(ac->cor & re)); + + /* Mask based on the read enable size */ + ret &= re; + + if (ac->post_read) { + ret = ac->post_read(reg, ret); + } + + if (debug) { + qemu_log("%s:%s: read of value %#" PRIx64 "\n", prefix, + ac->name, ret); + } + + return ret; +} + +void register_reset(RegisterInfo *reg) +{ + g_assert(reg); + + if (!reg->data || !reg->access) { + return; + } + + register_write_val(reg, reg->access->reset); +} diff --git a/include/hw/register.h b/include/hw/register.h new file mode 100644 index 0000000000..6a2bc756d1 --- /dev/null +++ b/include/hw/register.h @@ -0,0 +1,112 @@ +/* + * Register Definition API + * + * Copyright (c) 2016 Xilinx Inc. + * Copyright (c) 2013 Peter Crosthwaite + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#ifndef REGISTER_H +#define REGISTER_H + +#include "exec/memory.h" + +typedef struct RegisterInfo RegisterInfo; +typedef struct RegisterAccessInfo RegisterAccessInfo; + +/** + * Access description for a register that is part of guest accessible device + * state. + * + * @name: String name of the register + * @ro: whether or not the bit is read-only + * @w1c: bits with the common write 1 to clear semantic. + * @reset: reset value. + * @cor: Bits that are clear on read + * @rsvd: Bits that are reserved and should not be changed + * + * @pre_write: Pre write callback. Passed the value that's to be written, + * immediately before the actual write. The returned value is what is written, + * giving the handler a chance to modify the written value. + * @post_write: Post write callback. Passed the written value. Most write side + * effects should be implemented here. + * + * @post_read: Post read callback. Passes the value that is about to be returned + * for a read. The return value from this function is what is ultimately read, + * allowing this function to modify the value before return to the client. + */ + +struct RegisterAccessInfo { + const char *name; + uint64_t ro; + uint64_t w1c; + uint64_t reset; + uint64_t cor; + uint64_t rsvd; + uint64_t unimp; + + uint64_t (*pre_write)(RegisterInfo *reg, uint64_t val); + void (*post_write)(RegisterInfo *reg, uint64_t val); + + uint64_t (*post_read)(RegisterInfo *reg, uint64_t val); +}; + +/** + * A register that is part of guest accessible state + * @data: pointer to the register data. Will be cast + * to the relevant uint type depending on data_size. + * @data_size: Size of the register in bytes. Must be + * 1, 2, 4 or 8 + * + * @access: Access description of this register + * + * @debug: Whether or not verbose debug is enabled + * @prefix: String prefix for log and debug messages + * + * @opaque: Opaque data for the register + */ + +struct RegisterInfo { + /* */ + void *data; + int data_size; + + const RegisterAccessInfo *access; + + void *opaque; +}; + +/** + * write a value to a register, subject to its restrictions + * @reg: register to write to + * @val: value to write + * @we: write enable mask + * @prefix: The device prefix that should be printed before the register name + * @debug: Should the write operation debug information be printed? + */ + +void register_write(RegisterInfo *reg, uint64_t val, uint64_t we, + const char *prefix, bool debug); + +/** + * read a value from a register, subject to its restrictions + * @reg: register to read from + * @re: read enable mask + * @prefix: The device prefix that should be printed before the register name + * @debug: Should the read operation debug information be printed? + * returns: value read + */ + +uint64_t register_read(RegisterInfo *reg, uint64_t re, const char* prefix, + bool debug); + +/** + * reset a register + * @reg: register to reset + */ + +void register_reset(RegisterInfo *reg); + +#endif From 0b73c9bb066c6b66a9466ad9c3bbfd841477bf50 Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Mon, 4 Jul 2016 13:06:36 +0100 Subject: [PATCH 10/23] register: Add Memory API glue Add memory io handlers that glue the register API to the memory API. Just translation functions at this stage. Although it does allow for devices to be created without all-in-one mmio r/w handlers. This patch also adds the RegisterInfoArray struct, which allows all of the individual RegisterInfo structs to be grouped into a single memory region. Signed-off-by: Peter Crosthwaite Signed-off-by: Alistair Francis Message-id: f7704d8ac6ac0f469ed35401f8151a38bd01468b.1467053537.git.alistair.francis@xilinx.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/core/register.c | 59 +++++++++++++++++++++++++++++++++++++++++++ include/hw/register.h | 43 +++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/hw/core/register.c b/hw/core/register.c index 033b03c438..f7f63389d2 100644 --- a/hw/core/register.c +++ b/hw/core/register.c @@ -158,3 +158,62 @@ void register_reset(RegisterInfo *reg) register_write_val(reg, reg->access->reset); } + +void register_write_memory(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + RegisterInfoArray *reg_array = opaque; + RegisterInfo *reg = NULL; + uint64_t we; + int i; + + for (i = 0; i < reg_array->num_elements; i++) { + if (reg_array->r[i]->access->addr == addr) { + reg = reg_array->r[i]; + break; + } + } + + if (!reg) { + qemu_log_mask(LOG_GUEST_ERROR, "Write to unimplemented register at " \ + "address: %#" PRIx64 "\n", addr); + return; + } + + /* Generate appropriate write enable mask */ + if (reg->data_size < size) { + we = MAKE_64BIT_MASK(0, reg->data_size * 8); + } else { + we = MAKE_64BIT_MASK(0, size * 8); + } + + register_write(reg, value, we, reg_array->prefix, + reg_array->debug); +} + +uint64_t register_read_memory(void *opaque, hwaddr addr, + unsigned size) +{ + RegisterInfoArray *reg_array = opaque; + RegisterInfo *reg = NULL; + uint64_t read_val; + int i; + + for (i = 0; i < reg_array->num_elements; i++) { + if (reg_array->r[i]->access->addr == addr) { + reg = reg_array->r[i]; + break; + } + } + + if (!reg) { + qemu_log_mask(LOG_GUEST_ERROR, "Read to unimplemented register at " \ + "address: %#" PRIx64 "\n", addr); + return 0; + } + + read_val = register_read(reg, size * 8, reg_array->prefix, + reg_array->debug); + + return extract64(read_val, 0, size * 8); +} diff --git a/include/hw/register.h b/include/hw/register.h index 6a2bc756d1..00bbfe5997 100644 --- a/include/hw/register.h +++ b/include/hw/register.h @@ -15,6 +15,7 @@ typedef struct RegisterInfo RegisterInfo; typedef struct RegisterAccessInfo RegisterAccessInfo; +typedef struct RegisterInfoArray RegisterInfoArray; /** * Access description for a register that is part of guest accessible device @@ -51,6 +52,8 @@ struct RegisterAccessInfo { void (*post_write)(RegisterInfo *reg, uint64_t val); uint64_t (*post_read)(RegisterInfo *reg, uint64_t val); + + hwaddr addr; }; /** @@ -78,6 +81,25 @@ struct RegisterInfo { void *opaque; }; +/** + * This structure is used to group all of the individual registers which are + * modeled using the RegisterInfo structure. + * + * @r is an aray containing of all the relevent RegisterInfo structures. + * + * @num_elements is the number of elements in the array r + * + * @mem: optional Memory region for the register + */ + +struct RegisterInfoArray { + int num_elements; + RegisterInfo **r; + + bool debug; + const char *prefix; +}; + /** * write a value to a register, subject to its restrictions * @reg: register to write to @@ -109,4 +131,25 @@ uint64_t register_read(RegisterInfo *reg, uint64_t re, const char* prefix, void register_reset(RegisterInfo *reg); +/** + * Memory API MMIO write handler that will write to a Register API register. + * @opaque: RegisterInfo to write to + * @addr: Address to write + * @value: Value to write + * @size: Number of bytes to write + */ + +void register_write_memory(void *opaque, hwaddr addr, uint64_t value, + unsigned size); + +/** + * Memory API MMIO read handler that will read from a Register API register. + * @opaque: RegisterInfo to read from + * @addr: Address to read + * @size: Number of bytes to read + * returns: Value read from register + */ + +uint64_t register_read_memory(void *opaque, hwaddr addr, unsigned size); + #endif From 684204593d9b81ba4cd4c441b5ee395a29cff606 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 4 Jul 2016 13:06:36 +0100 Subject: [PATCH 11/23] register: Define REG and FIELD macros Define some macros that can be used for defining registers and fields. The REG32 macro will define A_FOO, for the byte address of a register as well as R_FOO for the uint32_t[] register number (A_FOO / 4). The FIELD macro will define FOO_BAR_MASK, FOO_BAR_SHIFT and FOO_BAR_LENGTH constants for field BAR in register FOO. Finally, there are some shorthand helpers for extracting/depositing fields from registers based on these naming schemes. Usage can greatly reduce the verbosity of device code. The deposit and extract macros (eg FIELD_EX32, FIELD_DP32 etc.) can be used to generate extract and deposits without any repetition of the name stems. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Signed-off-by: Alistair Francis Signed-off-by: Edgar E. Iglesias Message-id: bbd87a3c03b1f173b1ed73a6d502c0196c18a72f.1467053537.git.alistair.francis@xilinx.com [ EI Changes: * Add Deposit macros ] Signed-off-by: Edgar E. Iglesias Signed-off-by: Alistair Francis Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- include/hw/register.h | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/include/hw/register.h b/include/hw/register.h index 00bbfe5997..e8c58a1dbb 100644 --- a/include/hw/register.h +++ b/include/hw/register.h @@ -152,4 +152,50 @@ void register_write_memory(void *opaque, hwaddr addr, uint64_t value, uint64_t register_read_memory(void *opaque, hwaddr addr, unsigned size); +/* Define constants for a 32 bit register */ + +/* This macro will define A_FOO, for the byte address of a register + * as well as R_FOO for the uint32_t[] register number (A_FOO / 4). + */ +#define REG32(reg, addr) \ + enum { A_ ## reg = (addr) }; \ + enum { R_ ## reg = (addr) / 4 }; + +/* Define SHIFT, LENGTH and MASK constants for a field within a register */ + +/* This macro will define FOO_BAR_MASK, FOO_BAR_SHIFT and FOO_BAR_LENGTH + * constants for field BAR in register FOO. + */ +#define FIELD(reg, field, shift, length) \ + enum { R_ ## reg ## _ ## field ## _SHIFT = (shift)}; \ + enum { R_ ## reg ## _ ## field ## _LENGTH = (length)}; \ + enum { R_ ## reg ## _ ## field ## _MASK = \ + MAKE_64BIT_MASK(shift, length)}; + +/* Extract a field from a register */ +#define FIELD_EX32(storage, reg, field) \ + extract32((storage), R_ ## reg ## _ ## field ## _SHIFT, \ + R_ ## reg ## _ ## field ## _LENGTH) + +/* Extract a field from an array of registers */ +#define ARRAY_FIELD_EX32(regs, reg, field) \ + FIELD_EX32((regs)[R_ ## reg], reg, field) + +/* Deposit a register field. + * Assigning values larger then the target field will result in + * compilation warnings. + */ +#define FIELD_DP32(storage, reg, field, val) ({ \ + struct { \ + unsigned int v:R_ ## reg ## _ ## field ## _LENGTH; \ + } v = { .v = val }; \ + uint32_t d; \ + d = deposit32((storage), R_ ## reg ## _ ## field ## _SHIFT, \ + R_ ## reg ## _ ## field ## _LENGTH, v.v); \ + d; }) + +/* Deposit a field to array of registers. */ +#define ARRAY_FIELD_DP32(regs, reg, field, val) \ + (regs)[R_ ## reg] = FIELD_DP32((regs)[R_ ## reg], reg, field, val); + #endif From 49e14ddbcef3477c9c47900ef132a92d1ca4180c Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 4 Jul 2016 13:06:36 +0100 Subject: [PATCH 12/23] register: QOMify QOMify registers as a child of TYPE_DEVICE. This allows registers to define GPIOs. Define an init helper that will do QOM initialisation. Signed-off-by: Peter Crosthwaite Signed-off-by: Alistair Francis Reviewed-by: KONRAD Frederic Reviewed-by: Peter Maydell Message-id: 2545f71db26bf5586ca0c08a3e3cf1b217450552.1467053537.git.alistair.francis@xilinx.com Signed-off-by: Peter Maydell --- hw/core/register.c | 23 +++++++++++++++++++++++ include/hw/register.h | 14 ++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/hw/core/register.c b/hw/core/register.c index f7f63389d2..c7d71037e3 100644 --- a/hw/core/register.c +++ b/hw/core/register.c @@ -159,6 +159,17 @@ void register_reset(RegisterInfo *reg) register_write_val(reg, reg->access->reset); } +void register_init(RegisterInfo *reg) +{ + assert(reg); + + if (!reg->data || !reg->access) { + return; + } + + object_initialize((void *)reg, sizeof(*reg), TYPE_REGISTER); +} + void register_write_memory(void *opaque, hwaddr addr, uint64_t value, unsigned size) { @@ -217,3 +228,15 @@ uint64_t register_read_memory(void *opaque, hwaddr addr, return extract64(read_val, 0, size * 8); } + +static const TypeInfo register_info = { + .name = TYPE_REGISTER, + .parent = TYPE_DEVICE, +}; + +static void register_register_types(void) +{ + type_register_static(®ister_info); +} + +type_init(register_register_types) diff --git a/include/hw/register.h b/include/hw/register.h index e8c58a1dbb..61c53fbf0a 100644 --- a/include/hw/register.h +++ b/include/hw/register.h @@ -11,6 +11,7 @@ #ifndef REGISTER_H #define REGISTER_H +#include "hw/qdev-core.h" #include "exec/memory.h" typedef struct RegisterInfo RegisterInfo; @@ -72,6 +73,9 @@ struct RegisterAccessInfo { */ struct RegisterInfo { + /* */ + DeviceState parent_obj; + /* */ void *data; int data_size; @@ -81,6 +85,9 @@ struct RegisterInfo { void *opaque; }; +#define TYPE_REGISTER "qemu,register" +#define REGISTER(obj) OBJECT_CHECK(RegisterInfo, (obj), TYPE_REGISTER) + /** * This structure is used to group all of the individual registers which are * modeled using the RegisterInfo structure. @@ -131,6 +138,13 @@ uint64_t register_read(RegisterInfo *reg, uint64_t re, const char* prefix, void register_reset(RegisterInfo *reg); +/** + * Initialize a register. + * @reg: Register to initialize + */ + +void register_init(RegisterInfo *reg); + /** * Memory API MMIO write handler that will write to a Register API register. * @opaque: RegisterInfo to write to From a74229597e2c226a1a1f46a8926a0d2ec9c7574e Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 4 Jul 2016 13:06:36 +0100 Subject: [PATCH 13/23] register: Add block initialise helper Add a helper that will scan a static RegisterAccessInfo Array and populate a container MemoryRegion with registers as defined. Signed-off-by: Peter Crosthwaite Signed-off-by: Alistair Francis Message-id: 347b810b2799e413c98d5bbeca97bcb1557946c3.1467053537.git.alistair.francis@xilinx.com Signed-off-by: Peter Maydell --- hw/core/register.c | 45 +++++++++++++++++++++++++++++++++++++++++++ include/hw/register.h | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/hw/core/register.c b/hw/core/register.c index c7d71037e3..4bfbc508de 100644 --- a/hw/core/register.c +++ b/hw/core/register.c @@ -229,6 +229,51 @@ uint64_t register_read_memory(void *opaque, hwaddr addr, return extract64(read_val, 0, size * 8); } +RegisterInfoArray *register_init_block32(DeviceState *owner, + const RegisterAccessInfo *rae, + int num, RegisterInfo *ri, + uint32_t *data, + const MemoryRegionOps *ops, + bool debug_enabled, + uint64_t memory_size) +{ + const char *device_prefix = object_get_typename(OBJECT(owner)); + RegisterInfoArray *r_array = g_new0(RegisterInfoArray, 1); + int i; + + r_array->r = g_new0(RegisterInfo *, num); + r_array->num_elements = num; + r_array->debug = debug_enabled; + r_array->prefix = device_prefix; + + for (i = 0; i < num; i++) { + int index = rae[i].addr / 4; + RegisterInfo *r = &ri[index]; + + *r = (RegisterInfo) { + .data = &data[index], + .data_size = sizeof(uint32_t), + .access = &rae[i], + .opaque = owner, + }; + register_init(r); + + r_array->r[i] = r; + } + + memory_region_init_io(&r_array->mem, OBJECT(owner), ops, r_array, + device_prefix, memory_size); + + return r_array; +} + +void register_finalize_block(RegisterInfoArray *r_array) +{ + object_unparent(OBJECT(&r_array->mem)); + g_free(r_array->r); + g_free(r_array); +} + static const TypeInfo register_info = { .name = TYPE_REGISTER, .parent = TYPE_DEVICE, diff --git a/include/hw/register.h b/include/hw/register.h index 61c53fbf0a..8c12233b75 100644 --- a/include/hw/register.h +++ b/include/hw/register.h @@ -100,6 +100,8 @@ struct RegisterInfo { */ struct RegisterInfoArray { + MemoryRegion mem; + int num_elements; RegisterInfo **r; @@ -166,6 +168,44 @@ void register_write_memory(void *opaque, hwaddr addr, uint64_t value, uint64_t register_read_memory(void *opaque, hwaddr addr, unsigned size); +/** + * Init a block of registers into a container MemoryRegion. A + * number of constant register definitions are parsed to create a corresponding + * array of RegisterInfo's. + * + * @owner: device owning the registers + * @rae: Register definitions to init + * @num: number of registers to init (length of @rae) + * @ri: Register array to init, must already be allocated + * @data: Array to use for register data, must already be allocated + * @ops: Memory region ops to access registers. + * @debug enabled: turn on/off verbose debug information + * returns: A structure containing all of the registers and an initialized + * memory region (r_array->mem) the caller should add to a container. + */ + +RegisterInfoArray *register_init_block32(DeviceState *owner, + const RegisterAccessInfo *rae, + int num, RegisterInfo *ri, + uint32_t *data, + const MemoryRegionOps *ops, + bool debug_enabled, + uint64_t memory_size); + +/** + * This function should be called to cleanup the registers that were initialized + * when calling register_init_block32(). This function should only be called + * from the device's instance_finalize function. + * + * Any memory operations that the device performed that require cleanup (such + * as creating subregions) need to be called before calling this function. + * + * @r_array: A structure containing all of the registers, as returned by + * register_init_block32() + */ + +void register_finalize_block(RegisterInfoArray *r_array); + /* Define constants for a 32 bit register */ /* This macro will define A_FOO, for the byte address of a register From 034c2e69023007ac855a86ab5d91591f70506a62 Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Mon, 4 Jul 2016 13:06:37 +0100 Subject: [PATCH 14/23] dma: Add Xilinx Zynq devcfg device model Add a minimal model for the devcfg device which is part of Zynq. This model supports DMA capabilities and interrupt generation. Signed-off-by: Peter Crosthwaite Signed-off-by: Alistair Francis Reviewed-by: Peter Maydell Message-id: 83df49d8fa2d203a421ca71620809e4b04754e65.1467053537.git.alistair.francis@xilinx.com Signed-off-by: Peter Maydell --- default-configs/arm-softmmu.mak | 1 + hw/dma/Makefile.objs | 1 + hw/dma/xlnx-zynq-devcfg.c | 400 ++++++++++++++++++++++++++++++ include/hw/dma/xlnx-zynq-devcfg.h | 62 +++++ 4 files changed, 464 insertions(+) create mode 100644 hw/dma/xlnx-zynq-devcfg.c create mode 100644 include/hw/dma/xlnx-zynq-devcfg.h diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak index c5bcba754a..7a19863b18 100644 --- a/default-configs/arm-softmmu.mak +++ b/default-configs/arm-softmmu.mak @@ -66,6 +66,7 @@ CONFIG_PXA2XX=y CONFIG_BITBANG_I2C=y CONFIG_FRAMEBUFFER=y CONFIG_XILINX_SPIPS=y +CONFIG_ZYNQ_DEVCFG=y CONFIG_ARM11SCU=y CONFIG_A9SCU=y diff --git a/hw/dma/Makefile.objs b/hw/dma/Makefile.objs index 8b0823e593..087c8e6855 100644 --- a/hw/dma/Makefile.objs +++ b/hw/dma/Makefile.objs @@ -5,6 +5,7 @@ common-obj-$(CONFIG_PL330) += pl330.o common-obj-$(CONFIG_I82374) += i82374.o common-obj-$(CONFIG_I8257) += i8257.o common-obj-$(CONFIG_XILINX_AXI) += xilinx_axidma.o +common-obj-$(CONFIG_ZYNQ_DEVCFG) += xlnx-zynq-devcfg.o common-obj-$(CONFIG_ETRAXFS) += etraxfs_dma.o common-obj-$(CONFIG_STP2000) += sparc32_dma.o common-obj-$(CONFIG_SUN4M) += sun4m_iommu.o diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c new file mode 100644 index 0000000000..3b10523430 --- /dev/null +++ b/hw/dma/xlnx-zynq-devcfg.c @@ -0,0 +1,400 @@ +/* + * QEMU model of the Xilinx Zynq Devcfg Interface + * + * (C) 2011 PetaLogix Pty Ltd + * (C) 2014 Xilinx Inc. + * Written by Peter Crosthwaite + * + * 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 "qemu/osdep.h" +#include "hw/dma/xlnx-zynq-devcfg.h" +#include "qemu/bitops.h" +#include "sysemu/sysemu.h" +#include "sysemu/dma.h" +#include "qemu/log.h" + +#define FREQ_HZ 900000000 + +#define BTT_MAX 0x400 + +#ifndef XLNX_ZYNQ_DEVCFG_ERR_DEBUG +#define XLNX_ZYNQ_DEVCFG_ERR_DEBUG 0 +#endif + +#define DB_PRINT(fmt, args...) do { \ + if (XLNX_ZYNQ_DEVCFG_ERR_DEBUG) { \ + qemu_log("%s: " fmt, __func__, ## args); \ + } \ +} while (0); + +REG32(CTRL, 0x00) + FIELD(CTRL, FORCE_RST, 31, 1) /* Not supported, wr ignored */ + FIELD(CTRL, PCAP_PR, 27, 1) /* Forced to 0 on bad unlock */ + FIELD(CTRL, PCAP_MODE, 26, 1) + FIELD(CTRL, MULTIBOOT_EN, 24, 1) + FIELD(CTRL, USER_MODE, 15, 1) + FIELD(CTRL, PCFG_AES_FUSE, 12, 1) + FIELD(CTRL, PCFG_AES_EN, 9, 3) + FIELD(CTRL, SEU_EN, 8, 1) + FIELD(CTRL, SEC_EN, 7, 1) + FIELD(CTRL, SPNIDEN, 6, 1) + FIELD(CTRL, SPIDEN, 5, 1) + FIELD(CTRL, NIDEN, 4, 1) + FIELD(CTRL, DBGEN, 3, 1) + FIELD(CTRL, DAP_EN, 0, 3) + +REG32(LOCK, 0x04) +#define AES_FUSE_LOCK 4 +#define AES_EN_LOCK 3 +#define SEU_LOCK 2 +#define SEC_LOCK 1 +#define DBG_LOCK 0 + +/* mapping bits in R_LOCK to what they lock in R_CTRL */ +static const uint32_t lock_ctrl_map[] = { + [AES_FUSE_LOCK] = R_CTRL_PCFG_AES_FUSE_MASK, + [AES_EN_LOCK] = R_CTRL_PCFG_AES_EN_MASK, + [SEU_LOCK] = R_CTRL_SEU_EN_MASK, + [SEC_LOCK] = R_CTRL_SEC_EN_MASK, + [DBG_LOCK] = R_CTRL_SPNIDEN_MASK | R_CTRL_SPIDEN_MASK | + R_CTRL_NIDEN_MASK | R_CTRL_DBGEN_MASK | + R_CTRL_DAP_EN_MASK, +}; + +REG32(CFG, 0x08) + FIELD(CFG, RFIFO_TH, 10, 2) + FIELD(CFG, WFIFO_TH, 8, 2) + FIELD(CFG, RCLK_EDGE, 7, 1) + FIELD(CFG, WCLK_EDGE, 6, 1) + FIELD(CFG, DISABLE_SRC_INC, 5, 1) + FIELD(CFG, DISABLE_DST_INC, 4, 1) +#define R_CFG_RESET 0x50B + +REG32(INT_STS, 0x0C) + FIELD(INT_STS, PSS_GTS_USR_B, 31, 1) + FIELD(INT_STS, PSS_FST_CFG_B, 30, 1) + FIELD(INT_STS, PSS_CFG_RESET_B, 27, 1) + FIELD(INT_STS, RX_FIFO_OV, 18, 1) + FIELD(INT_STS, WR_FIFO_LVL, 17, 1) + FIELD(INT_STS, RD_FIFO_LVL, 16, 1) + FIELD(INT_STS, DMA_CMD_ERR, 15, 1) + FIELD(INT_STS, DMA_Q_OV, 14, 1) + FIELD(INT_STS, DMA_DONE, 13, 1) + FIELD(INT_STS, DMA_P_DONE, 12, 1) + FIELD(INT_STS, P2D_LEN_ERR, 11, 1) + FIELD(INT_STS, PCFG_DONE, 2, 1) +#define R_INT_STS_RSVD ((0x7 << 24) | (0x1 << 19) | (0xF < 7)) + +REG32(INT_MASK, 0x10) + +REG32(STATUS, 0x14) + FIELD(STATUS, DMA_CMD_Q_F, 31, 1) + FIELD(STATUS, DMA_CMD_Q_E, 30, 1) + FIELD(STATUS, DMA_DONE_CNT, 28, 2) + FIELD(STATUS, RX_FIFO_LVL, 20, 5) + FIELD(STATUS, TX_FIFO_LVL, 12, 7) + FIELD(STATUS, PSS_GTS_USR_B, 11, 1) + FIELD(STATUS, PSS_FST_CFG_B, 10, 1) + FIELD(STATUS, PSS_CFG_RESET_B, 5, 1) + +REG32(DMA_SRC_ADDR, 0x18) +REG32(DMA_DST_ADDR, 0x1C) +REG32(DMA_SRC_LEN, 0x20) +REG32(DMA_DST_LEN, 0x24) +REG32(ROM_SHADOW, 0x28) +REG32(SW_ID, 0x30) +REG32(UNLOCK, 0x34) + +#define R_UNLOCK_MAGIC 0x757BDF0D + +REG32(MCTRL, 0x80) + FIELD(MCTRL, PS_VERSION, 28, 4) + FIELD(MCTRL, PCFG_POR_B, 8, 1) + FIELD(MCTRL, INT_PCAP_LPBK, 4, 1) + FIELD(MCTRL, QEMU, 3, 1) + +static void xlnx_zynq_devcfg_update_ixr(XlnxZynqDevcfg *s) +{ + qemu_set_irq(s->irq, ~s->regs[R_INT_MASK] & s->regs[R_INT_STS]); +} + +static void xlnx_zynq_devcfg_reset(DeviceState *dev) +{ + XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(dev); + int i; + + for (i = 0; i < XLNX_ZYNQ_DEVCFG_R_MAX; ++i) { + register_reset(&s->regs_info[i]); + } +} + +static void xlnx_zynq_devcfg_dma_go(XlnxZynqDevcfg *s) +{ + do { + uint8_t buf[BTT_MAX]; + XlnxZynqDevcfgDMACmd *dmah = s->dma_cmd_fifo; + uint32_t btt = BTT_MAX; + bool loopback = s->regs[R_MCTRL] & R_MCTRL_INT_PCAP_LPBK_MASK; + + btt = MIN(btt, dmah->src_len); + if (loopback) { + btt = MIN(btt, dmah->dest_len); + } + DB_PRINT("reading %x bytes from %x\n", btt, dmah->src_addr); + dma_memory_read(&address_space_memory, dmah->src_addr, buf, btt); + dmah->src_len -= btt; + dmah->src_addr += btt; + if (loopback && (dmah->src_len || dmah->dest_len)) { + DB_PRINT("writing %x bytes from %x\n", btt, dmah->dest_addr); + dma_memory_write(&address_space_memory, dmah->dest_addr, buf, btt); + dmah->dest_len -= btt; + dmah->dest_addr += btt; + } + if (!dmah->src_len && !dmah->dest_len) { + DB_PRINT("dma operation finished\n"); + s->regs[R_INT_STS] |= R_INT_STS_DMA_DONE_MASK | + R_INT_STS_DMA_P_DONE_MASK; + s->dma_cmd_fifo_num--; + memmove(s->dma_cmd_fifo, &s->dma_cmd_fifo[1], + sizeof(s->dma_cmd_fifo) - sizeof(s->dma_cmd_fifo[0])); + } + xlnx_zynq_devcfg_update_ixr(s); + } while (s->dma_cmd_fifo_num); +} + +static void r_ixr_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque); + + xlnx_zynq_devcfg_update_ixr(s); +} + +static uint64_t r_ctrl_pre_write(RegisterInfo *reg, uint64_t val) +{ + XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque); + int i; + + for (i = 0; i < ARRAY_SIZE(lock_ctrl_map); ++i) { + if (s->regs[R_LOCK] & 1 << i) { + val &= ~lock_ctrl_map[i]; + val |= lock_ctrl_map[i] & s->regs[R_CTRL]; + } + } + return val; +} + +static void r_ctrl_post_write(RegisterInfo *reg, uint64_t val) +{ + const char *device_prefix = object_get_typename(OBJECT(reg->opaque)); + uint32_t aes_en = FIELD_EX32(val, CTRL, PCFG_AES_EN); + + if (aes_en != 0 && aes_en != 7) { + qemu_log_mask(LOG_UNIMP, "%s: warning, aes-en bits inconsistent," + "unimplemented security reset should happen!\n", + device_prefix); + } +} + +static void r_unlock_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque); + const char *device_prefix = object_get_typename(OBJECT(s)); + + if (val == R_UNLOCK_MAGIC) { + DB_PRINT("successful unlock\n"); + s->regs[R_CTRL] |= R_CTRL_PCAP_PR_MASK; + s->regs[R_CTRL] |= R_CTRL_PCFG_AES_EN_MASK; + memory_region_set_enabled(&s->iomem, true); + } else { /* bad unlock attempt */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: failed unlock\n", device_prefix); + s->regs[R_CTRL] &= ~R_CTRL_PCAP_PR_MASK; + s->regs[R_CTRL] &= ~R_CTRL_PCFG_AES_EN_MASK; + /* core becomes inaccessible */ + memory_region_set_enabled(&s->iomem, false); + } +} + +static uint64_t r_lock_pre_write(RegisterInfo *reg, uint64_t val) +{ + XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque); + + /* once bits are locked they stay locked */ + return s->regs[R_LOCK] | val; +} + +static void r_dma_dst_len_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque); + + s->dma_cmd_fifo[s->dma_cmd_fifo_num] = (XlnxZynqDevcfgDMACmd) { + .src_addr = s->regs[R_DMA_SRC_ADDR] & ~0x3UL, + .dest_addr = s->regs[R_DMA_DST_ADDR] & ~0x3UL, + .src_len = s->regs[R_DMA_SRC_LEN] << 2, + .dest_len = s->regs[R_DMA_DST_LEN] << 2, + }; + s->dma_cmd_fifo_num++; + DB_PRINT("dma transfer started; %d total transfers pending\n", + s->dma_cmd_fifo_num); + xlnx_zynq_devcfg_dma_go(s); +} + +static const RegisterAccessInfo xlnx_zynq_devcfg_regs_info[] = { + { .name = "CTRL", .addr = A_CTRL, + .reset = R_CTRL_PCAP_PR_MASK | R_CTRL_PCAP_MODE_MASK | 0x3 << 13, + .rsvd = 0x1 << 28 | 0x3ff << 13 | 0x3 << 13, + .pre_write = r_ctrl_pre_write, + .post_write = r_ctrl_post_write, + }, + { .name = "LOCK", .addr = A_LOCK, + .rsvd = MAKE_64BIT_MASK(5, 64 - 5), + .pre_write = r_lock_pre_write, + }, + { .name = "CFG", .addr = A_CFG, + .reset = R_CFG_RESET, + .rsvd = 0xfffff00f, + }, + { .name = "INT_STS", .addr = A_INT_STS, + .w1c = ~R_INT_STS_RSVD, + .reset = R_INT_STS_PSS_GTS_USR_B_MASK | + R_INT_STS_PSS_CFG_RESET_B_MASK | + R_INT_STS_WR_FIFO_LVL_MASK, + .rsvd = R_INT_STS_RSVD, + .post_write = r_ixr_post_write, + }, + { .name = "INT_MASK", .addr = A_INT_MASK, + .reset = ~0, + .rsvd = R_INT_STS_RSVD, + .post_write = r_ixr_post_write, + }, + { .name = "STATUS", .addr = A_STATUS, + .reset = R_STATUS_DMA_CMD_Q_E_MASK | + R_STATUS_PSS_GTS_USR_B_MASK | + R_STATUS_PSS_CFG_RESET_B_MASK, + .ro = ~0, + }, + { .name = "DMA_SRC_ADDR", .addr = A_DMA_SRC_ADDR, }, + { .name = "DMA_DST_ADDR", .addr = A_DMA_DST_ADDR, }, + { .name = "DMA_SRC_LEN", .addr = A_DMA_SRC_LEN, + .ro = MAKE_64BIT_MASK(27, 64 - 27) }, + { .name = "DMA_DST_LEN", .addr = A_DMA_DST_LEN, + .ro = MAKE_64BIT_MASK(27, 64 - 27), + .post_write = r_dma_dst_len_post_write, + }, + { .name = "ROM_SHADOW", .addr = A_ROM_SHADOW, + .rsvd = ~0ull, + }, + { .name = "SW_ID", .addr = A_SW_ID, }, + { .name = "UNLOCK", .addr = A_UNLOCK, + .post_write = r_unlock_post_write, + }, + { .name = "MCTRL", .addr = R_MCTRL * 4, + /* Silicon 3.0 for version field, the mysterious reserved bit 23 + * and QEMU platform identifier. + */ + .reset = 0x2 << R_MCTRL_PS_VERSION_SHIFT | 1 << 23 | R_MCTRL_QEMU_MASK, + .ro = ~R_MCTRL_INT_PCAP_LPBK_MASK, + .rsvd = 0x00f00303, + }, +}; + +static const MemoryRegionOps xlnx_zynq_devcfg_reg_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + +static const VMStateDescription vmstate_xlnx_zynq_devcfg_dma_cmd = { + .name = "xlnx_zynq_devcfg_dma_cmd", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(src_addr, XlnxZynqDevcfgDMACmd), + VMSTATE_UINT32(dest_addr, XlnxZynqDevcfgDMACmd), + VMSTATE_UINT32(src_len, XlnxZynqDevcfgDMACmd), + VMSTATE_UINT32(dest_len, XlnxZynqDevcfgDMACmd), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_xlnx_zynq_devcfg = { + .name = "xlnx_zynq_devcfg", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(dma_cmd_fifo, XlnxZynqDevcfg, + XLNX_ZYNQ_DEVCFG_DMA_CMD_FIFO_LEN, 0, + vmstate_xlnx_zynq_devcfg_dma_cmd, + XlnxZynqDevcfgDMACmd), + VMSTATE_UINT8(dma_cmd_fifo_num, XlnxZynqDevcfg), + VMSTATE_UINT32_ARRAY(regs, XlnxZynqDevcfg, XLNX_ZYNQ_DEVCFG_R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void xlnx_zynq_devcfg_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(obj); + RegisterInfoArray *reg_array; + + sysbus_init_irq(sbd, &s->irq); + + memory_region_init(&s->iomem, obj, "devcfg", XLNX_ZYNQ_DEVCFG_R_MAX * 4); + reg_array = + register_init_block32(DEVICE(obj), xlnx_zynq_devcfg_regs_info, + ARRAY_SIZE(xlnx_zynq_devcfg_regs_info), + s->regs_info, s->regs, + &xlnx_zynq_devcfg_reg_ops, + XLNX_ZYNQ_DEVCFG_ERR_DEBUG, + XLNX_ZYNQ_DEVCFG_R_MAX); + memory_region_add_subregion(&s->iomem, + A_CTRL, + ®_array->mem); + + sysbus_init_mmio(sbd, &s->iomem); +} + +static void xlnx_zynq_devcfg_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = xlnx_zynq_devcfg_reset; + dc->vmsd = &vmstate_xlnx_zynq_devcfg; +} + +static const TypeInfo xlnx_zynq_devcfg_info = { + .name = TYPE_XLNX_ZYNQ_DEVCFG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XlnxZynqDevcfg), + .instance_init = xlnx_zynq_devcfg_init, + .class_init = xlnx_zynq_devcfg_class_init, +}; + +static void xlnx_zynq_devcfg_register_types(void) +{ + type_register_static(&xlnx_zynq_devcfg_info); +} + +type_init(xlnx_zynq_devcfg_register_types) diff --git a/include/hw/dma/xlnx-zynq-devcfg.h b/include/hw/dma/xlnx-zynq-devcfg.h new file mode 100644 index 0000000000..d40e5c8df6 --- /dev/null +++ b/include/hw/dma/xlnx-zynq-devcfg.h @@ -0,0 +1,62 @@ +/* + * QEMU model of the Xilinx Devcfg Interface + * + * (C) 2011 PetaLogix Pty Ltd + * (C) 2014 Xilinx Inc. + * Written by Peter Crosthwaite + * + * 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. + */ + +#ifndef XLNX_ZYNQ_DEVCFG_H + +#include "hw/register.h" +#include "hw/sysbus.h" + +#define TYPE_XLNX_ZYNQ_DEVCFG "xlnx.ps7-dev-cfg" + +#define XLNX_ZYNQ_DEVCFG(obj) \ + OBJECT_CHECK(XlnxZynqDevcfg, (obj), TYPE_XLNX_ZYNQ_DEVCFG) + +#define XLNX_ZYNQ_DEVCFG_R_MAX 0x118 + +#define XLNX_ZYNQ_DEVCFG_DMA_CMD_FIFO_LEN 10 + +typedef struct XlnxZynqDevcfgDMACmd { + uint32_t src_addr; + uint32_t dest_addr; + uint32_t src_len; + uint32_t dest_len; +} XlnxZynqDevcfgDMACmd; + +typedef struct XlnxZynqDevcfg { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + + XlnxZynqDevcfgDMACmd dma_cmd_fifo[XLNX_ZYNQ_DEVCFG_DMA_CMD_FIFO_LEN]; + uint8_t dma_cmd_fifo_num; + + uint32_t regs[XLNX_ZYNQ_DEVCFG_R_MAX]; + RegisterInfo regs_info[XLNX_ZYNQ_DEVCFG_R_MAX]; +} XlnxZynqDevcfg; + +#define XLNX_ZYNQ_DEVCFG_H +#endif From f4b99537f1caac9864e3e93ac5a980e5d62ece0f Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 4 Jul 2016 13:06:37 +0100 Subject: [PATCH 15/23] xilinx_zynq: Connect devcfg to the Zynq machine model Signed-off-by: Peter Crosthwaite Signed-off-by: Alistair Francis Reviewed-by: Peter Maydell Message-id: 85f39c9a13569b1113dacac3b952b0af54fc1260.1467053537.git.alistair.francis@xilinx.com Signed-off-by: Peter Maydell --- hw/arm/xilinx_zynq.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index aefebcfa6d..f26c2733e3 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -294,6 +294,12 @@ static void zynq_init(MachineState *machine) sysbus_connect_irq(busdev, n + 1, pic[dma_irqs[n] - IRQ_OFFSET]); } + dev = qdev_create(NULL, "xlnx.ps7-dev-cfg"); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_connect_irq(busdev, 0, pic[40 - IRQ_OFFSET]); + sysbus_mmio_map(busdev, 0, 0xF8007000); + zynq_binfo.ram_size = ram_size; zynq_binfo.kernel_filename = kernel_filename; zynq_binfo.kernel_cmdline = kernel_cmdline; From 7673bb4cd305637b37bd0c0b79dd3bf6deb55172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Mon, 4 Jul 2016 13:06:37 +0100 Subject: [PATCH 16/23] ssi: change ssi_slave_init to be a realize ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This enables qemu to handle late inits and report errors. All the SSI slave routine names were changed accordingly. Code was modified to handle errors when possible (m25p80 and ssi-sd) Tested with the m25p80 slave object. Suggested-by: Paolo Bonzini Signed-off-by: Cédric Le Goater Message-id: 1467138270-32481-2-git-send-email-clg@kaod.org Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/arm/spitz.c | 12 ++++-------- hw/arm/tosa.c | 5 ++--- hw/arm/z2.c | 6 ++---- hw/block/m25p80.c | 12 +++++------- hw/display/ads7846.c | 5 ++--- hw/display/ssd0323.c | 5 ++--- hw/misc/max111x.c | 12 ++++++------ hw/sd/ssi-sd.c | 9 +++++---- hw/ssi/ssi.c | 6 +++--- include/hw/ssi/ssi.h | 2 +- 10 files changed, 32 insertions(+), 42 deletions(-) diff --git a/hw/arm/spitz.c b/hw/arm/spitz.c index ba40f8302b..41cc2eeeb1 100644 --- a/hw/arm/spitz.c +++ b/hw/arm/spitz.c @@ -598,15 +598,13 @@ static uint32_t spitz_lcdtg_transfer(SSISlave *dev, uint32_t value) return 0; } -static int spitz_lcdtg_init(SSISlave *dev) +static void spitz_lcdtg_realize(SSISlave *dev, Error **errp) { SpitzLCDTG *s = FROM_SSI_SLAVE(SpitzLCDTG, dev); spitz_lcdtg = s; s->bl_power = 0; s->bl_intensity = 0x20; - - return 0; } /* SSP devices */ @@ -666,7 +664,7 @@ static void spitz_adc_temp_on(void *opaque, int line, int level) max111x_set_input(max1111, MAX1111_BATT_TEMP, 0); } -static int corgi_ssp_init(SSISlave *d) +static void corgi_ssp_realize(SSISlave *d, Error **errp) { DeviceState *dev = DEVICE(d); CorgiSSPState *s = FROM_SSI_SLAVE(CorgiSSPState, d); @@ -675,8 +673,6 @@ static int corgi_ssp_init(SSISlave *d) s->bus[0] = ssi_create_bus(dev, "ssi0"); s->bus[1] = ssi_create_bus(dev, "ssi1"); s->bus[2] = ssi_create_bus(dev, "ssi2"); - - return 0; } static void spitz_ssp_attach(PXA2xxState *cpu) @@ -1121,7 +1117,7 @@ static void corgi_ssp_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = corgi_ssp_init; + k->realize = corgi_ssp_realize; k->transfer = corgi_ssp_transfer; dc->vmsd = &vmstate_corgi_ssp_regs; } @@ -1150,7 +1146,7 @@ static void spitz_lcdtg_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = spitz_lcdtg_init; + k->realize = spitz_lcdtg_realize; k->transfer = spitz_lcdtg_transfer; dc->vmsd = &vmstate_spitz_lcdtg_regs; } diff --git a/hw/arm/tosa.c b/hw/arm/tosa.c index 4e9494f94c..2db66508b5 100644 --- a/hw/arm/tosa.c +++ b/hw/arm/tosa.c @@ -127,10 +127,9 @@ static uint32_t tosa_ssp_tansfer(SSISlave *dev, uint32_t value) return 0; } -static int tosa_ssp_init(SSISlave *dev) +static void tosa_ssp_realize(SSISlave *dev, Error **errp) { /* Nothing to do. */ - return 0; } #define TYPE_TOSA_DAC "tosa_dac" @@ -283,7 +282,7 @@ static void tosa_ssp_class_init(ObjectClass *klass, void *data) { SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = tosa_ssp_init; + k->realize = tosa_ssp_realize; k->transfer = tosa_ssp_tansfer; } diff --git a/hw/arm/z2.c b/hw/arm/z2.c index aea895a500..68a92f3184 100644 --- a/hw/arm/z2.c +++ b/hw/arm/z2.c @@ -151,14 +151,12 @@ static void z2_lcd_cs(void *opaque, int line, int level) z2_lcd->selected = !level; } -static int zipit_lcd_init(SSISlave *dev) +static void zipit_lcd_realize(SSISlave *dev, Error **errp) { ZipitLCD *z = FROM_SSI_SLAVE(ZipitLCD, dev); z->selected = 0; z->enabled = 0; z->pos = 0; - - return 0; } static VMStateDescription vmstate_zipit_lcd_state = { @@ -181,7 +179,7 @@ static void zipit_lcd_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = zipit_lcd_init; + k->realize = zipit_lcd_realize; k->transfer = zipit_lcd_transfer; dc->vmsd = &vmstate_zipit_lcd_state; } diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c index 326b688e83..3cdcfce07b 100644 --- a/hw/block/m25p80.c +++ b/hw/block/m25p80.c @@ -28,6 +28,7 @@ #include "hw/ssi/ssi.h" #include "qemu/bitops.h" #include "qemu/log.h" +#include "qapi/error.h" #ifndef M25P80_ERR_DEBUG #define M25P80_ERR_DEBUG 0 @@ -1132,7 +1133,7 @@ static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx) return r; } -static int m25p80_init(SSISlave *ss) +static void m25p80_realize(SSISlave *ss, Error **errp) { DriveInfo *dinfo; Flash *s = M25P80(ss); @@ -1153,18 +1154,15 @@ static int m25p80_init(SSISlave *ss) s->storage = blk_blockalign(s->blk, s->size); - /* FIXME: Move to late init */ if (blk_pread(s->blk, 0, s->storage, s->size) != s->size) { - fprintf(stderr, "Failed to initialize SPI flash!\n"); - return 1; + error_setg(errp, "failed to read the initial flash content"); + return; } } else { DB_PRINT_L(0, "No BDRV - binding to RAM\n"); s->storage = blk_blockalign(NULL, s->size); memset(s->storage, 0xFF, s->size); } - - return 0; } static void m25p80_reset(DeviceState *d) @@ -1224,7 +1222,7 @@ static void m25p80_class_init(ObjectClass *klass, void *data) SSISlaveClass *k = SSI_SLAVE_CLASS(klass); M25P80Class *mc = M25P80_CLASS(klass); - k->init = m25p80_init; + k->realize = m25p80_realize; k->transfer = m25p80_transfer8; k->set_cs = m25p80_cs; k->cs_polarity = SSI_CS_LOW; diff --git a/hw/display/ads7846.c b/hw/display/ads7846.c index 05aa2d1e6b..166edade7d 100644 --- a/hw/display/ads7846.c +++ b/hw/display/ads7846.c @@ -133,7 +133,7 @@ static const VMStateDescription vmstate_ads7846 = { } }; -static int ads7846_init(SSISlave *d) +static void ads7846_realize(SSISlave *d, Error **errp) { DeviceState *dev = DEVICE(d); ADS7846State *s = FROM_SSI_SLAVE(ADS7846State, d); @@ -152,14 +152,13 @@ static int ads7846_init(SSISlave *d) ads7846_int_update(s); vmstate_register(NULL, -1, &vmstate_ads7846, s); - return 0; } static void ads7846_class_init(ObjectClass *klass, void *data) { SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = ads7846_init; + k->realize = ads7846_realize; k->transfer = ads7846_transfer; } diff --git a/hw/display/ssd0323.c b/hw/display/ssd0323.c index 14c1bf339c..6d1faf44af 100644 --- a/hw/display/ssd0323.c +++ b/hw/display/ssd0323.c @@ -361,7 +361,7 @@ static const GraphicHwOps ssd0323_ops = { .gfx_update = ssd0323_update_display, }; -static int ssd0323_init(SSISlave *d) +static void ssd0323_realize(SSISlave *d, Error **errp) { DeviceState *dev = DEVICE(d); ssd0323_state *s = FROM_SSI_SLAVE(ssd0323_state, d); @@ -375,14 +375,13 @@ static int ssd0323_init(SSISlave *d) register_savevm(dev, "ssd0323_oled", -1, 1, ssd0323_save, ssd0323_load, s); - return 0; } static void ssd0323_class_init(ObjectClass *klass, void *data) { SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = ssd0323_init; + k->realize = ssd0323_realize; k->transfer = ssd0323_transfer; k->cs_polarity = SSI_CS_HIGH; } diff --git a/hw/misc/max111x.c b/hw/misc/max111x.c index 9014f0f705..2a277bdb86 100644 --- a/hw/misc/max111x.c +++ b/hw/misc/max111x.c @@ -147,14 +147,14 @@ static int max111x_init(SSISlave *d, int inputs) return 0; } -static int max1110_init(SSISlave *dev) +static void max1110_realize(SSISlave *dev, Error **errp) { - return max111x_init(dev, 8); + max111x_init(dev, 8); } -static int max1111_init(SSISlave *dev) +static void max1111_realize(SSISlave *dev, Error **errp) { - return max111x_init(dev, 4); + max111x_init(dev, 4); } void max111x_set_input(DeviceState *dev, int line, uint8_t value) @@ -183,7 +183,7 @@ static void max1110_class_init(ObjectClass *klass, void *data) { SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = max1110_init; + k->realize = max1110_realize; } static const TypeInfo max1110_info = { @@ -196,7 +196,7 @@ static void max1111_class_init(ObjectClass *klass, void *data) { SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = max1111_init; + k->realize = max1111_realize; } static const TypeInfo max1111_info = { diff --git a/hw/sd/ssi-sd.c b/hw/sd/ssi-sd.c index 075e4ed5df..3ff0886dd5 100644 --- a/hw/sd/ssi-sd.c +++ b/hw/sd/ssi-sd.c @@ -15,6 +15,7 @@ #include "sysemu/blockdev.h" #include "hw/ssi/ssi.h" #include "hw/sd/sd.h" +#include "qapi/error.h" //#define DEBUG_SSI_SD 1 @@ -249,7 +250,7 @@ static int ssi_sd_load(QEMUFile *f, void *opaque, int version_id) return 0; } -static int ssi_sd_init(SSISlave *d) +static void ssi_sd_realize(SSISlave *d, Error **errp) { DeviceState *dev = DEVICE(d); ssi_sd_state *s = FROM_SSI_SLAVE(ssi_sd_state, d); @@ -260,17 +261,17 @@ static int ssi_sd_init(SSISlave *d) dinfo = drive_get_next(IF_SD); s->sd = sd_init(dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, true); if (s->sd == NULL) { - return -1; + error_setg(errp, "Device initialization failed."); + return; } register_savevm(dev, "ssi_sd", -1, 1, ssi_sd_save, ssi_sd_load, s); - return 0; } static void ssi_sd_class_init(ObjectClass *klass, void *data) { SSISlaveClass *k = SSI_SLAVE_CLASS(klass); - k->init = ssi_sd_init; + k->realize = ssi_sd_realize; k->transfer = ssi_sd_transfer; k->cs_polarity = SSI_CS_LOW; } diff --git a/hw/ssi/ssi.c b/hw/ssi/ssi.c index 9791c0d947..7eaaf565fd 100644 --- a/hw/ssi/ssi.c +++ b/hw/ssi/ssi.c @@ -54,7 +54,7 @@ static uint32_t ssi_transfer_raw_default(SSISlave *dev, uint32_t val) return 0; } -static int ssi_slave_init(DeviceState *dev) +static void ssi_slave_realize(DeviceState *dev, Error **errp) { SSISlave *s = SSI_SLAVE(dev); SSISlaveClass *ssc = SSI_SLAVE_GET_CLASS(s); @@ -64,7 +64,7 @@ static int ssi_slave_init(DeviceState *dev) qdev_init_gpio_in_named(dev, ssi_cs_default, SSI_GPIO_CS, 1); } - return ssc->init(s); + ssc->realize(s, errp); } static void ssi_slave_class_init(ObjectClass *klass, void *data) @@ -72,7 +72,7 @@ static void ssi_slave_class_init(ObjectClass *klass, void *data) SSISlaveClass *ssc = SSI_SLAVE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - dc->init = ssi_slave_init; + dc->realize = ssi_slave_realize; dc->bus_type = TYPE_SSI_BUS; if (!ssc->transfer_raw) { ssc->transfer_raw = ssi_transfer_raw_default; diff --git a/include/hw/ssi/ssi.h b/include/hw/ssi/ssi.h index 4a0a53903c..6a0c3c3cdb 100644 --- a/include/hw/ssi/ssi.h +++ b/include/hw/ssi/ssi.h @@ -37,7 +37,7 @@ enum SSICSMode { struct SSISlaveClass { DeviceClass parent_class; - int (*init)(SSISlave *dev); + void (*realize)(SSISlave *dev, Error **errp); /* if you have standard or no CS behaviour, just override transfer. * This is called when the device cs is active (true by default). From cace7b801d49430522840fa13e9ac2bdc2dcf941 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 4 Jul 2016 13:06:37 +0100 Subject: [PATCH 17/23] m25p80: do not put iovec on the stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When doing a read-modify-write cycle, QEMU uses the iovec after returning from blk_aio_pwritev. m25p80 puts the iovec on the stack of blk_aio_pwritev's caller, which causes trouble in this case. This has been a problem since commit 243e6f6 ("m25p80: Switch to byte-based block access", 2016-05-12) started doing writes at a smaller granularity than 512 bytes. In principle however it could have broken before when using -drive if=mtd,cache=none on a disk with 4K native sectors. Signed-off-by: Paolo Bonzini Reviewed-by: Cédric Le Goater Reviewed-by: Eric Blake Signed-off-by: Cédric Le Goater Message-id: 1467138270-32481-3-git-send-email-clg@kaod.org Signed-off-by: Peter Maydell --- hw/block/m25p80.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c index 3cdcfce07b..8e4c115735 100644 --- a/hw/block/m25p80.c +++ b/hw/block/m25p80.c @@ -447,6 +447,11 @@ static inline Manufacturer get_man(Flash *s) static void blk_sync_complete(void *opaque, int ret) { + QEMUIOVector *iov = opaque; + + qemu_iovec_destroy(iov); + g_free(iov); + /* do nothing. Masters do not directly interact with the backing store, * only the working copy so no mutexing required. */ @@ -454,31 +459,31 @@ static void blk_sync_complete(void *opaque, int ret) static void flash_sync_page(Flash *s, int page) { - QEMUIOVector iov; + QEMUIOVector *iov = g_new(QEMUIOVector, 1); if (!s->blk || blk_is_read_only(s->blk)) { return; } - qemu_iovec_init(&iov, 1); - qemu_iovec_add(&iov, s->storage + page * s->pi->page_size, + qemu_iovec_init(iov, 1); + qemu_iovec_add(iov, s->storage + page * s->pi->page_size, s->pi->page_size); - blk_aio_pwritev(s->blk, page * s->pi->page_size, &iov, 0, - blk_sync_complete, NULL); + blk_aio_pwritev(s->blk, page * s->pi->page_size, iov, 0, + blk_sync_complete, iov); } static inline void flash_sync_area(Flash *s, int64_t off, int64_t len) { - QEMUIOVector iov; + QEMUIOVector *iov = g_new(QEMUIOVector, 1); if (!s->blk || blk_is_read_only(s->blk)) { return; } assert(!(len % BDRV_SECTOR_SIZE)); - qemu_iovec_init(&iov, 1); - qemu_iovec_add(&iov, s->storage + off, len); - blk_aio_pwritev(s->blk, off, &iov, 0, blk_sync_complete, NULL); + qemu_iovec_init(iov, 1); + qemu_iovec_add(iov, s->storage + off, len); + blk_aio_pwritev(s->blk, off, iov, 0, blk_sync_complete, iov); } static void flash_erase(Flash *s, int offset, FlashCMD cmd) From b68cb06093a36bd6fbd4d06cd62c08629fea2242 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 4 Jul 2016 13:06:37 +0100 Subject: [PATCH 18/23] m25p80: avoid out of bounds accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit s->cur_addr can be made to point outside s->storage, either by writing a value >= 128 to s->ear (because s->ear * MAX_3BYTES_SIZE is a signed integer and sign-extends into the 64-bit cur_addr), or just by writing an address beyond the size of the flash being emulated. Avoid the sign extension to make the code cleaner, and on top of that mask s->cur_addr to s->size. Signed-off-by: Paolo Bonzini Reviewed-by: Cédric Le Goater Signed-off-by: Cédric Le Goater Message-id: 1467138270-32481-4-git-send-email-clg@kaod.org Reviewed by: Marcin Krzeminski Signed-off-by: Cédric Le Goater Signed-off-by: Peter Maydell --- hw/block/m25p80.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c index 8e4c115735..4836b4eb96 100644 --- a/hw/block/m25p80.c +++ b/hw/block/m25p80.c @@ -587,18 +587,16 @@ static inline int get_addr_length(Flash *s) static void complete_collecting_data(Flash *s) { - int i; + int i, n; - s->cur_addr = 0; - - for (i = 0; i < get_addr_length(s); ++i) { + n = get_addr_length(s); + s->cur_addr = (n == 3 ? s->ear : 0); + for (i = 0; i < n; ++i) { s->cur_addr <<= 8; s->cur_addr |= s->data[i]; } - if (get_addr_length(s) == 3) { - s->cur_addr += s->ear * MAX_3BYTES_SIZE; - } + s->cur_addr &= s->size - 1; s->state = STATE_IDLE; @@ -1100,14 +1098,14 @@ static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx) DB_PRINT_L(1, "page program cur_addr=%#" PRIx64 " data=%" PRIx8 "\n", s->cur_addr, (uint8_t)tx); flash_write8(s, s->cur_addr, (uint8_t)tx); - s->cur_addr++; + s->cur_addr = (s->cur_addr + 1) & (s->size - 1); break; case STATE_READ: r = s->storage[s->cur_addr]; DB_PRINT_L(1, "READ 0x%" PRIx64 "=%" PRIx8 "\n", s->cur_addr, (uint8_t)r); - s->cur_addr = (s->cur_addr + 1) % s->size; + s->cur_addr = (s->cur_addr + 1) & (s->size - 1); break; case STATE_COLLECTING_DATA: From b7f480c3f69edc74dd8e2ecfc51c9e35590965de Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 4 Jul 2016 13:06:37 +0100 Subject: [PATCH 19/23] m25p80: change cur_addr to 32 bit integer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maximum amount of storage that can be addressed by the m25p80 command set is 4 GiB. However, cur_addr is currently a 64-bit integer. To avoid further problems related to sign extension of signed 32-bit integer expressions, change cur_addr to a 32 bit integer. Preserve migration format by adding a dummy 4-byte field in place of the (big-endian) high four bytes in the formerly 64-bit cur_addr field. Signed-off-by: Paolo Bonzini Reviewed-by: Cédric Le Goater Signed-off-by: Cédric Le Goater Message-id: 1467138270-32481-5-git-send-email-clg@kaod.org Signed-off-by: Peter Maydell --- hw/block/m25p80.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c index 4836b4eb96..80e60c23e0 100644 --- a/hw/block/m25p80.c +++ b/hw/block/m25p80.c @@ -390,7 +390,7 @@ typedef struct Flash { uint32_t pos; uint8_t needed_bytes; uint8_t cmd_in_progress; - uint64_t cur_addr; + uint32_t cur_addr; uint32_t nonvolatile_cfg; /* Configuration register for Macronix */ uint32_t volatile_cfg; @@ -536,9 +536,9 @@ static inline void flash_sync_dirty(Flash *s, int64_t newpage) } static inline -void flash_write8(Flash *s, uint64_t addr, uint8_t data) +void flash_write8(Flash *s, uint32_t addr, uint8_t data) { - int64_t page = addr / s->pi->page_size; + uint32_t page = addr / s->pi->page_size; uint8_t prev = s->storage[s->cur_addr]; if (!s->write_enable) { @@ -546,7 +546,7 @@ void flash_write8(Flash *s, uint64_t addr, uint8_t data) } if ((prev ^ data) & data) { - DB_PRINT_L(1, "programming zero to one! addr=%" PRIx64 " %" PRIx8 + DB_PRINT_L(1, "programming zero to one! addr=%" PRIx32 " %" PRIx8 " -> %" PRIx8 "\n", addr, prev, data); } @@ -1095,7 +1095,7 @@ static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx) switch (s->state) { case STATE_PAGE_PROGRAM: - DB_PRINT_L(1, "page program cur_addr=%#" PRIx64 " data=%" PRIx8 "\n", + DB_PRINT_L(1, "page program cur_addr=%#" PRIx32 " data=%" PRIx8 "\n", s->cur_addr, (uint8_t)tx); flash_write8(s, s->cur_addr, (uint8_t)tx); s->cur_addr = (s->cur_addr + 1) & (s->size - 1); @@ -1103,7 +1103,7 @@ static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx) case STATE_READ: r = s->storage[s->cur_addr]; - DB_PRINT_L(1, "READ 0x%" PRIx64 "=%" PRIx8 "\n", s->cur_addr, + DB_PRINT_L(1, "READ 0x%" PRIx32 "=%" PRIx8 "\n", s->cur_addr, (uint8_t)r); s->cur_addr = (s->cur_addr + 1) & (s->size - 1); break; @@ -1202,7 +1202,8 @@ static const VMStateDescription vmstate_m25p80 = { VMSTATE_UINT32(pos, Flash), VMSTATE_UINT8(needed_bytes, Flash), VMSTATE_UINT8(cmd_in_progress, Flash), - VMSTATE_UINT64(cur_addr, Flash), + VMSTATE_UNUSED(4), + VMSTATE_UINT32(cur_addr, Flash), VMSTATE_BOOL(write_enable, Flash), VMSTATE_BOOL_V(reset_enable, Flash, 2), VMSTATE_UINT8_V(ear, Flash, 2), From 73bce5187ba93b34944aab08d1da5712c1183a68 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 4 Jul 2016 13:06:37 +0100 Subject: [PATCH 20/23] m25p80: qdev-ify drive property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows specifying the property via -drive if=none and creating the flash device with -device. Signed-off-by: Paolo Bonzini Signed-off-by: Cédric Le Goater Message-id: 1467138270-32481-6-git-send-email-clg@kaod.org [clg: added an extra fix for sabrelite_init() keeping the test on flash_dev did not seem necessary. ] Signed-off-by: Cédric Le Goater Signed-off-by: Peter Maydell --- hw/arm/sabrelite.c | 16 +++++++++++----- hw/arm/xilinx_zynq.c | 8 +++++++- hw/arm/xlnx-ep108.c | 9 ++++++++- hw/block/m25p80.c | 10 ++-------- hw/microblaze/petalogix_ml605_mmu.c | 9 ++++++++- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/hw/arm/sabrelite.c b/hw/arm/sabrelite.c index 776c51e398..4e7ac8cc4f 100644 --- a/hw/arm/sabrelite.c +++ b/hw/arm/sabrelite.c @@ -86,13 +86,19 @@ static void sabrelite_init(MachineState *machine) spi_bus = (SSIBus *)qdev_get_child_bus(DEVICE(spi_dev), "spi"); if (spi_bus) { DeviceState *flash_dev; + qemu_irq cs_line; + DriveInfo *dinfo = drive_get_next(IF_MTD); - flash_dev = ssi_create_slave(spi_bus, "sst25vf016b"); - if (flash_dev) { - qemu_irq cs_line = qdev_get_gpio_in_named(flash_dev, - SSI_GPIO_CS, 0); - sysbus_connect_irq(SYS_BUS_DEVICE(spi_dev), 1, cs_line); + flash_dev = ssi_create_slave_no_init(spi_bus, "sst25vf016b"); + if (dinfo) { + qdev_prop_set_drive(flash_dev, "drive", + blk_by_legacy_dinfo(dinfo), + &error_fatal); } + qdev_init_nofail(flash_dev); + + cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0); + sysbus_connect_irq(SYS_BUS_DEVICE(spi_dev), 1, cs_line); } } } diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index f26c2733e3..7dac20d67d 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -138,7 +138,13 @@ static inline void zynq_init_spi_flashes(uint32_t base_addr, qemu_irq irq, spi = (SSIBus *)qdev_get_child_bus(dev, bus_name); for (j = 0; j < num_ss; ++j) { - flash_dev = ssi_create_slave(spi, "n25q128"); + DriveInfo *dinfo = drive_get_next(IF_MTD); + flash_dev = ssi_create_slave_no_init(spi, "n25q128"); + if (dinfo) { + qdev_prop_set_drive(flash_dev, "drive", + blk_by_legacy_dinfo(dinfo), &error_fatal); + } + qdev_init_nofail(flash_dev); cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0); sysbus_connect_irq(busdev, i * num_ss + j + 1, cs_line); diff --git a/hw/arm/xlnx-ep108.c b/hw/arm/xlnx-ep108.c index 34b4641712..4ec590a25d 100644 --- a/hw/arm/xlnx-ep108.c +++ b/hw/arm/xlnx-ep108.c @@ -88,12 +88,19 @@ static void xlnx_ep108_init(MachineState *machine) SSIBus *spi_bus; DeviceState *flash_dev; qemu_irq cs_line; + DriveInfo *dinfo = drive_get_next(IF_MTD); gchar *bus_name = g_strdup_printf("spi%d", i); spi_bus = (SSIBus *)qdev_get_child_bus(DEVICE(&s->soc), bus_name); g_free(bus_name); - flash_dev = ssi_create_slave(spi_bus, "sst25wf080"); + flash_dev = ssi_create_slave_no_init(spi_bus, "sst25wf080"); + if (dinfo) { + qdev_prop_set_drive(flash_dev, "drive", blk_by_legacy_dinfo(dinfo), + &error_fatal); + } + qdev_init_nofail(flash_dev); + cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0); sysbus_connect_irq(SYS_BUS_DEVICE(&s->soc.spi[i]), 1, cs_line); diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c index 80e60c23e0..d9b27939dd 100644 --- a/hw/block/m25p80.c +++ b/hw/block/m25p80.c @@ -1138,7 +1138,6 @@ static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx) static void m25p80_realize(SSISlave *ss, Error **errp) { - DriveInfo *dinfo; Flash *s = M25P80(ss); M25P80Class *mc = M25P80_GET_CLASS(s); @@ -1147,14 +1146,8 @@ static void m25p80_realize(SSISlave *ss, Error **errp) s->size = s->pi->sector_size * s->pi->n_sectors; s->dirty_page = -1; - /* FIXME use a qdev drive property instead of drive_get_next() */ - dinfo = drive_get_next(IF_MTD); - - if (dinfo) { + if (s->blk) { DB_PRINT_L(0, "Binding to IF_MTD drive\n"); - s->blk = blk_by_legacy_dinfo(dinfo); - blk_attach_dev_nofail(s->blk, s); - s->storage = blk_blockalign(s->blk, s->size); if (blk_pread(s->blk, 0, s->storage, s->size) != s->size) { @@ -1187,6 +1180,7 @@ static Property m25p80_properties[] = { DEFINE_PROP_UINT8("spansion-cr2nv", Flash, spansion_cr2nv, 0x8), DEFINE_PROP_UINT8("spansion-cr3nv", Flash, spansion_cr3nv, 0x2), DEFINE_PROP_UINT8("spansion-cr4nv", Flash, spansion_cr4nv, 0x10), + DEFINE_PROP_DRIVE("drive", Flash, blk), DEFINE_PROP_END_OF_LIST(), }; diff --git a/hw/microblaze/petalogix_ml605_mmu.c b/hw/microblaze/petalogix_ml605_mmu.c index 07527b677b..4968bdbb28 100644 --- a/hw/microblaze/petalogix_ml605_mmu.c +++ b/hw/microblaze/petalogix_ml605_mmu.c @@ -191,9 +191,16 @@ petalogix_ml605_init(MachineState *machine) spi = (SSIBus *)qdev_get_child_bus(dev, "spi"); for (i = 0; i < NUM_SPI_FLASHES; i++) { + DriveInfo *dinfo = drive_get_next(IF_MTD); qemu_irq cs_line; - dev = ssi_create_slave(spi, "n25q128"); + dev = ssi_create_slave_no_init(spi, "n25q128"); + if (dinfo) { + qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(dinfo), + &error_fatal); + } + qdev_init_nofail(dev); + cs_line = qdev_get_gpio_in_named(dev, SSI_GPIO_CS, 0); sysbus_connect_irq(busdev, i+1, cs_line); } From 7c1c69bca43c345481b778382e605e1d49a12d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Mon, 4 Jul 2016 13:06:37 +0100 Subject: [PATCH 21/23] ast2400: add SMC controllers (FMC and SPI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Aspeed AST2400 soc includes a static memory controller for the BMC which supports NOR, NAND and SPI flash memory modules. This controller has two modes : the SMC for the legacy interface which supports only one module and the FMC for the new interface which supports up to five modules. The AST2400 also includes a SPI only controller used for the host firmware, commonly called BIOS on Intel. It can be used in three mode : a SPI master, SPI slave and SPI pass-through Below is the initial framework for the SMC controller (FMC mode only) and the SPI controller: the sysbus object, MMIO for registers configuration and controls. Each controller has a SPI bus and a configurable number of CS lines for SPI flash slaves. The differences between the controllers are small, so they are abstracted using indirections on the register numbers. Only SPI flash modules are supported. Signed-off-by: Cédric Le Goater Message-id: 1467138270-32481-7-git-send-email-clg@kaod.org Reviewed-by: Peter Maydell [PMM: added one missing error_propagate] Signed-off-by: Peter Maydell --- hw/arm/ast2400.c | 35 +++- hw/ssi/Makefile.objs | 1 + hw/ssi/aspeed_smc.c | 326 ++++++++++++++++++++++++++++++++++++ include/hw/arm/ast2400.h | 3 + include/hw/ssi/aspeed_smc.h | 79 +++++++++ 5 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 hw/ssi/aspeed_smc.c create mode 100644 include/hw/ssi/aspeed_smc.h diff --git a/hw/arm/ast2400.c b/hw/arm/ast2400.c index b14a82fcde..ed8fcb7fb5 100644 --- a/hw/arm/ast2400.c +++ b/hw/arm/ast2400.c @@ -23,6 +23,9 @@ #define AST2400_UART_5_BASE 0x00184000 #define AST2400_IOMEM_SIZE 0x00200000 #define AST2400_IOMEM_BASE 0x1E600000 +#define AST2400_SMC_BASE AST2400_IOMEM_BASE /* Legacy SMC */ +#define AST2400_FMC_BASE 0X1E620000 +#define AST2400_SPI_BASE 0X1E630000 #define AST2400_VIC_BASE 0x1E6C0000 #define AST2400_SCU_BASE 0x1E6E2000 #define AST2400_TIMER_BASE 0x1E782000 @@ -85,13 +88,21 @@ static void ast2400_init(Object *obj) "hw-strap1", &error_abort); object_property_add_alias(obj, "hw-strap2", OBJECT(&s->scu), "hw-strap2", &error_abort); + + object_initialize(&s->smc, sizeof(s->smc), "aspeed.smc.fmc"); + object_property_add_child(obj, "smc", OBJECT(&s->smc), NULL); + qdev_set_parent_bus(DEVICE(&s->smc), sysbus_get_default()); + + object_initialize(&s->spi, sizeof(s->spi), "aspeed.smc.spi"); + object_property_add_child(obj, "spi", OBJECT(&s->spi), NULL); + qdev_set_parent_bus(DEVICE(&s->spi), sysbus_get_default()); } static void ast2400_realize(DeviceState *dev, Error **errp) { int i; AST2400State *s = AST2400(dev); - Error *err = NULL; + Error *err = NULL, *local_err = NULL; /* IO space */ memory_region_init_io(&s->iomem, NULL, &ast2400_io_ops, NULL, @@ -147,6 +158,28 @@ static void ast2400_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c), 0, AST2400_I2C_BASE); sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c), 0, qdev_get_gpio_in(DEVICE(&s->vic), 12)); + + /* SMC */ + object_property_set_int(OBJECT(&s->smc), 1, "num-cs", &err); + object_property_set_bool(OBJECT(&s->smc), true, "realized", &local_err); + error_propagate(&err, local_err); + if (err) { + error_propagate(errp, err); + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->smc), 0, AST2400_FMC_BASE); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->smc), 0, + qdev_get_gpio_in(DEVICE(&s->vic), 19)); + + /* SPI */ + object_property_set_int(OBJECT(&s->spi), 1, "num-cs", &err); + object_property_set_bool(OBJECT(&s->spi), true, "realized", &local_err); + error_propagate(&err, local_err); + if (err) { + error_propagate(errp, err); + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi), 0, AST2400_SPI_BASE); } static void ast2400_class_init(ObjectClass *oc, void *data) diff --git a/hw/ssi/Makefile.objs b/hw/ssi/Makefile.objs index fcbb79ef01..c79a8dcd86 100644 --- a/hw/ssi/Makefile.objs +++ b/hw/ssi/Makefile.objs @@ -2,6 +2,7 @@ common-obj-$(CONFIG_PL022) += pl022.o common-obj-$(CONFIG_SSI) += ssi.o common-obj-$(CONFIG_XILINX_SPI) += xilinx_spi.o common-obj-$(CONFIG_XILINX_SPIPS) += xilinx_spips.o +common-obj-$(CONFIG_ASPEED_SOC) += aspeed_smc.o obj-$(CONFIG_OMAP) += omap_spi.o obj-$(CONFIG_IMX) += imx_spi.o diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c new file mode 100644 index 0000000000..537635e18d --- /dev/null +++ b/hw/ssi/aspeed_smc.c @@ -0,0 +1,326 @@ +/* + * ASPEED AST2400 SMC Controller (SPI Flash Only) + * + * Copyright (C) 2016 IBM Corp. + * + * 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 "qemu/osdep.h" +#include "hw/sysbus.h" +#include "sysemu/sysemu.h" +#include "qemu/log.h" +#include "include/qemu/error-report.h" +#include "exec/address-spaces.h" + +#include "hw/ssi/aspeed_smc.h" + +/* CE Type Setting Register */ +#define R_CONF (0x00 / 4) +#define CONF_LEGACY_DISABLE (1 << 31) +#define CONF_ENABLE_W4 20 +#define CONF_ENABLE_W3 19 +#define CONF_ENABLE_W2 18 +#define CONF_ENABLE_W1 17 +#define CONF_ENABLE_W0 16 +#define CONF_FLASH_TYPE4 9 +#define CONF_FLASH_TYPE3 7 +#define CONF_FLASH_TYPE2 5 +#define CONF_FLASH_TYPE1 3 +#define CONF_FLASH_TYPE0 1 + +/* CE Control Register */ +#define R_CE_CTRL (0x04 / 4) +#define CTRL_EXTENDED4 4 /* 32 bit addressing for SPI */ +#define CTRL_EXTENDED3 3 /* 32 bit addressing for SPI */ +#define CTRL_EXTENDED2 2 /* 32 bit addressing for SPI */ +#define CTRL_EXTENDED1 1 /* 32 bit addressing for SPI */ +#define CTRL_EXTENDED0 0 /* 32 bit addressing for SPI */ + +/* Interrupt Control and Status Register */ +#define R_INTR_CTRL (0x08 / 4) +#define INTR_CTRL_DMA_STATUS (1 << 11) +#define INTR_CTRL_CMD_ABORT_STATUS (1 << 10) +#define INTR_CTRL_WRITE_PROTECT_STATUS (1 << 9) +#define INTR_CTRL_DMA_EN (1 << 3) +#define INTR_CTRL_CMD_ABORT_EN (1 << 2) +#define INTR_CTRL_WRITE_PROTECT_EN (1 << 1) + +/* CEx Control Register */ +#define R_CTRL0 (0x10 / 4) +#define CTRL_CMD_SHIFT 16 +#define CTRL_CMD_MASK 0xff +#define CTRL_CE_STOP_ACTIVE (1 << 2) +#define CTRL_CMD_MODE_MASK 0x3 +#define CTRL_READMODE 0x0 +#define CTRL_FREADMODE 0x1 +#define CTRL_WRITEMODE 0x2 +#define CTRL_USERMODE 0x3 +#define R_CTRL1 (0x14 / 4) +#define R_CTRL2 (0x18 / 4) +#define R_CTRL3 (0x1C / 4) +#define R_CTRL4 (0x20 / 4) + +/* CEx Segment Address Register */ +#define R_SEG_ADDR0 (0x30 / 4) +#define SEG_SIZE_SHIFT 24 /* 8MB units */ +#define SEG_SIZE_MASK 0x7f +#define SEG_START_SHIFT 16 /* address bit [A29-A23] */ +#define SEG_START_MASK 0x7f +#define R_SEG_ADDR1 (0x34 / 4) +#define R_SEG_ADDR2 (0x38 / 4) +#define R_SEG_ADDR3 (0x3C / 4) +#define R_SEG_ADDR4 (0x40 / 4) + +/* Misc Control Register #1 */ +#define R_MISC_CTRL1 (0x50 / 4) + +/* Misc Control Register #2 */ +#define R_MISC_CTRL2 (0x54 / 4) + +/* DMA Control/Status Register */ +#define R_DMA_CTRL (0x80 / 4) +#define DMA_CTRL_DELAY_MASK 0xf +#define DMA_CTRL_DELAY_SHIFT 8 +#define DMA_CTRL_FREQ_MASK 0xf +#define DMA_CTRL_FREQ_SHIFT 4 +#define DMA_CTRL_MODE (1 << 3) +#define DMA_CTRL_CKSUM (1 << 2) +#define DMA_CTRL_DIR (1 << 1) +#define DMA_CTRL_EN (1 << 0) + +/* DMA Flash Side Address */ +#define R_DMA_FLASH_ADDR (0x84 / 4) + +/* DMA DRAM Side Address */ +#define R_DMA_DRAM_ADDR (0x88 / 4) + +/* DMA Length Register */ +#define R_DMA_LEN (0x8C / 4) + +/* Checksum Calculation Result */ +#define R_DMA_CHECKSUM (0x90 / 4) + +/* Misc Control Register #2 */ +#define R_TIMINGS (0x94 / 4) + +/* SPI controller registers and bits */ +#define R_SPI_CONF (0x00 / 4) +#define SPI_CONF_ENABLE_W0 0 +#define R_SPI_CTRL0 (0x4 / 4) +#define R_SPI_MISC_CTRL (0x10 / 4) +#define R_SPI_TIMINGS (0x14 / 4) + +static const AspeedSMCController controllers[] = { + { "aspeed.smc.smc", R_CONF, R_CE_CTRL, R_CTRL0, R_TIMINGS, + CONF_ENABLE_W0, 5 }, + { "aspeed.smc.fmc", R_CONF, R_CE_CTRL, R_CTRL0, R_TIMINGS, + CONF_ENABLE_W0, 5 }, + { "aspeed.smc.spi", R_SPI_CONF, 0xff, R_SPI_CTRL0, R_SPI_TIMINGS, + SPI_CONF_ENABLE_W0, 1 }, +}; + +static bool aspeed_smc_is_ce_stop_active(const AspeedSMCState *s, int cs) +{ + return s->regs[s->r_ctrl0 + cs] & CTRL_CE_STOP_ACTIVE; +} + +static void aspeed_smc_update_cs(const AspeedSMCState *s) +{ + int i; + + for (i = 0; i < s->num_cs; ++i) { + qemu_set_irq(s->cs_lines[i], aspeed_smc_is_ce_stop_active(s, i)); + } +} + +static void aspeed_smc_reset(DeviceState *d) +{ + AspeedSMCState *s = ASPEED_SMC(d); + int i; + + memset(s->regs, 0, sizeof s->regs); + + /* Unselect all slaves */ + for (i = 0; i < s->num_cs; ++i) { + s->regs[s->r_ctrl0 + i] |= CTRL_CE_STOP_ACTIVE; + } + + aspeed_smc_update_cs(s); +} + +static bool aspeed_smc_is_implemented(AspeedSMCState *s, hwaddr addr) +{ + return (addr == s->r_conf || addr == s->r_timings || addr == s->r_ce_ctrl || + (addr >= s->r_ctrl0 && addr < s->r_ctrl0 + s->num_cs)); +} + +static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size) +{ + AspeedSMCState *s = ASPEED_SMC(opaque); + + addr >>= 2; + + if (addr >= ARRAY_SIZE(s->regs)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds read at 0x%" HWADDR_PRIx "\n", + __func__, addr); + return 0; + } + + if (!aspeed_smc_is_implemented(s, addr)) { + qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n", + __func__, addr); + return 0; + } + + return s->regs[addr]; +} + +static void aspeed_smc_write(void *opaque, hwaddr addr, uint64_t data, + unsigned int size) +{ + AspeedSMCState *s = ASPEED_SMC(opaque); + uint32_t value = data; + + addr >>= 2; + + if (addr >= ARRAY_SIZE(s->regs)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds write at 0x%" HWADDR_PRIx "\n", + __func__, addr); + return; + } + + if (!aspeed_smc_is_implemented(s, addr)) { + qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n", + __func__, addr); + return; + } + + /* + * Not much to do apart from storing the value and set the cs + * lines if the register is a controlling one. + */ + s->regs[addr] = value; + if (addr >= s->r_ctrl0 && addr < s->r_ctrl0 + s->num_cs) { + aspeed_smc_update_cs(s); + } +} + +static const MemoryRegionOps aspeed_smc_ops = { + .read = aspeed_smc_read, + .write = aspeed_smc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.unaligned = true, +}; + +static void aspeed_smc_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + AspeedSMCState *s = ASPEED_SMC(dev); + AspeedSMCClass *mc = ASPEED_SMC_GET_CLASS(s); + int i; + + s->ctrl = mc->ctrl; + + /* keep a copy under AspeedSMCState to speed up accesses */ + s->r_conf = s->ctrl->r_conf; + s->r_ce_ctrl = s->ctrl->r_ce_ctrl; + s->r_ctrl0 = s->ctrl->r_ctrl0; + s->r_timings = s->ctrl->r_timings; + s->conf_enable_w0 = s->ctrl->conf_enable_w0; + + /* Enforce some real HW limits */ + if (s->num_cs > s->ctrl->max_slaves) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: num_cs cannot exceed: %d\n", + __func__, s->ctrl->max_slaves); + s->num_cs = s->ctrl->max_slaves; + } + + s->spi = ssi_create_bus(dev, "spi"); + + /* Setup cs_lines for slaves */ + sysbus_init_irq(sbd, &s->irq); + s->cs_lines = g_new0(qemu_irq, s->num_cs); + ssi_auto_connect_slaves(dev, s->cs_lines, s->spi); + + for (i = 0; i < s->num_cs; ++i) { + sysbus_init_irq(sbd, &s->cs_lines[i]); + } + + aspeed_smc_reset(dev); + + memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_smc_ops, s, + s->ctrl->name, ASPEED_SMC_R_MAX * 4); + sysbus_init_mmio(sbd, &s->mmio); +} + +static const VMStateDescription vmstate_aspeed_smc = { + .name = "aspeed.smc", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, AspeedSMCState, ASPEED_SMC_R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static Property aspeed_smc_properties[] = { + DEFINE_PROP_UINT32("num-cs", AspeedSMCState, num_cs, 1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void aspeed_smc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + AspeedSMCClass *mc = ASPEED_SMC_CLASS(klass); + + dc->realize = aspeed_smc_realize; + dc->reset = aspeed_smc_reset; + dc->props = aspeed_smc_properties; + dc->vmsd = &vmstate_aspeed_smc; + mc->ctrl = data; +} + +static const TypeInfo aspeed_smc_info = { + .name = TYPE_ASPEED_SMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedSMCState), + .class_size = sizeof(AspeedSMCClass), + .abstract = true, +}; + +static void aspeed_smc_register_types(void) +{ + int i; + + type_register_static(&aspeed_smc_info); + for (i = 0; i < ARRAY_SIZE(controllers); ++i) { + TypeInfo ti = { + .name = controllers[i].name, + .parent = TYPE_ASPEED_SMC, + .class_init = aspeed_smc_class_init, + .class_data = (void *)&controllers[i], + }; + type_register(&ti); + } +} + +type_init(aspeed_smc_register_types) diff --git a/include/hw/arm/ast2400.h b/include/hw/arm/ast2400.h index f1a64fd389..7833bc716c 100644 --- a/include/hw/arm/ast2400.h +++ b/include/hw/arm/ast2400.h @@ -17,6 +17,7 @@ #include "hw/misc/aspeed_scu.h" #include "hw/timer/aspeed_timer.h" #include "hw/i2c/aspeed_i2c.h" +#include "hw/ssi/aspeed_smc.h" typedef struct AST2400State { /*< private >*/ @@ -29,6 +30,8 @@ typedef struct AST2400State { AspeedTimerCtrlState timerctrl; AspeedI2CState i2c; AspeedSCUState scu; + AspeedSMCState smc; + AspeedSMCState spi; } AST2400State; #define TYPE_AST2400 "ast2400" diff --git a/include/hw/ssi/aspeed_smc.h b/include/hw/ssi/aspeed_smc.h new file mode 100644 index 0000000000..c4a4960cd8 --- /dev/null +++ b/include/hw/ssi/aspeed_smc.h @@ -0,0 +1,79 @@ +/* + * ASPEED AST2400 SMC Controller (SPI Flash Only) + * + * Copyright (C) 2016 IBM Corp. + * + * 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. + */ + +#ifndef ASPEED_SMC_H +#define ASPEED_SMC_H + +#include "hw/ssi/ssi.h" + +typedef struct AspeedSMCController { + const char *name; + uint8_t r_conf; + uint8_t r_ce_ctrl; + uint8_t r_ctrl0; + uint8_t r_timings; + uint8_t conf_enable_w0; + uint8_t max_slaves; +} AspeedSMCController; + +#define TYPE_ASPEED_SMC "aspeed.smc" +#define ASPEED_SMC(obj) OBJECT_CHECK(AspeedSMCState, (obj), TYPE_ASPEED_SMC) +#define ASPEED_SMC_CLASS(klass) \ + OBJECT_CLASS_CHECK(AspeedSMCClass, (klass), TYPE_ASPEED_SMC) +#define ASPEED_SMC_GET_CLASS(obj) \ + OBJECT_GET_CLASS(AspeedSMCClass, (obj), TYPE_ASPEED_SMC) + +typedef struct AspeedSMCClass { + SysBusDevice parent_obj; + const AspeedSMCController *ctrl; +} AspeedSMCClass; + +#define ASPEED_SMC_R_MAX (0x100 / 4) + +typedef struct AspeedSMCState { + SysBusDevice parent_obj; + + const AspeedSMCController *ctrl; + + MemoryRegion mmio; + + qemu_irq irq; + int irqline; + + uint32_t num_cs; + qemu_irq *cs_lines; + + SSIBus *spi; + + uint32_t regs[ASPEED_SMC_R_MAX]; + + /* depends on the controller type */ + uint8_t r_conf; + uint8_t r_ce_ctrl; + uint8_t r_ctrl0; + uint8_t r_timings; + uint8_t conf_enable_w0; +} AspeedSMCState; + +#endif /* ASPEED_SMC_H */ From 924ed16386ac8e079a9798f7de3b0d933fc3132c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Mon, 4 Jul 2016 13:06:38 +0100 Subject: [PATCH 22/23] ast2400: add SPI flash slaves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each controller on the ast2400 has a memory range on which it maps its flash module slaves. Each slave is assigned a memory segment for its mapping that can be changed at bootime with the Segment Address Register. This is not supported in the current implementation so we are using the defaults provided by the specs. Each SPI flash slave can then be accessed in two modes: Command and User. When in User mode, accesses to the memory segment of the slaves are translated in SPI transfers. When in Command mode, the HW generates the SPI commands automatically and the memory segment is accessed as if doing a MMIO. Other SPI controllers call that mode linear addressing mode. For this purpose, we are adding below each crontoller an array of structs gathering for each SPI flash module, a segment rank, a MemoryRegion to handle the memory accesses and the associated SPI slave device, which should be a m25p80. Only the User mode is supported for now but we are preparing ground for the Command mode. The framework is sufficient to support Linux. Signed-off-by: Cédric Le Goater Message-id: 1467138270-32481-8-git-send-email-clg@kaod.org [PMM: Use g_new0() rather than g_malloc0()] Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/arm/ast2400.c | 5 ++ hw/ssi/aspeed_smc.c | 150 +++++++++++++++++++++++++++++++++++- include/hw/ssi/aspeed_smc.h | 21 +++++ 3 files changed, 173 insertions(+), 3 deletions(-) diff --git a/hw/arm/ast2400.c b/hw/arm/ast2400.c index ed8fcb7fb5..0555843620 100644 --- a/hw/arm/ast2400.c +++ b/hw/arm/ast2400.c @@ -31,6 +31,9 @@ #define AST2400_TIMER_BASE 0x1E782000 #define AST2400_I2C_BASE 0x1E78A000 +#define AST2400_FMC_FLASH_BASE 0x20000000 +#define AST2400_SPI_FLASH_BASE 0x30000000 + #define AST2400_A0_SILICON_REV 0x02000303 static const int uart_irqs[] = { 9, 32, 33, 34, 10 }; @@ -168,6 +171,7 @@ static void ast2400_realize(DeviceState *dev, Error **errp) return; } sysbus_mmio_map(SYS_BUS_DEVICE(&s->smc), 0, AST2400_FMC_BASE); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->smc), 1, AST2400_FMC_FLASH_BASE); sysbus_connect_irq(SYS_BUS_DEVICE(&s->smc), 0, qdev_get_gpio_in(DEVICE(&s->vic), 19)); @@ -180,6 +184,7 @@ static void ast2400_realize(DeviceState *dev, Error **errp) return; } sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi), 0, AST2400_SPI_BASE); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi), 1, AST2400_SPI_FLASH_BASE); } static void ast2400_class_init(ObjectClass *oc, void *data) diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c index 537635e18d..a371e302d4 100644 --- a/hw/ssi/aspeed_smc.c +++ b/hw/ssi/aspeed_smc.c @@ -127,13 +127,129 @@ #define R_SPI_MISC_CTRL (0x10 / 4) #define R_SPI_TIMINGS (0x14 / 4) +/* + * Default segments mapping addresses and size for each slave per + * controller. These can be changed when board is initialized with the + * Segment Address Registers but they don't seem do be used on the + * field. + */ +static const AspeedSegments aspeed_segments_legacy[] = { + { 0x10000000, 32 * 1024 * 1024 }, +}; + +static const AspeedSegments aspeed_segments_fmc[] = { + { 0x20000000, 64 * 1024 * 1024 }, + { 0x24000000, 32 * 1024 * 1024 }, + { 0x26000000, 32 * 1024 * 1024 }, + { 0x28000000, 32 * 1024 * 1024 }, + { 0x2A000000, 32 * 1024 * 1024 } +}; + +static const AspeedSegments aspeed_segments_spi[] = { + { 0x30000000, 64 * 1024 * 1024 }, +}; + static const AspeedSMCController controllers[] = { { "aspeed.smc.smc", R_CONF, R_CE_CTRL, R_CTRL0, R_TIMINGS, - CONF_ENABLE_W0, 5 }, + CONF_ENABLE_W0, 5, aspeed_segments_legacy, 0x6000000 }, { "aspeed.smc.fmc", R_CONF, R_CE_CTRL, R_CTRL0, R_TIMINGS, - CONF_ENABLE_W0, 5 }, + CONF_ENABLE_W0, 5, aspeed_segments_fmc, 0x10000000 }, { "aspeed.smc.spi", R_SPI_CONF, 0xff, R_SPI_CTRL0, R_SPI_TIMINGS, - SPI_CONF_ENABLE_W0, 1 }, + SPI_CONF_ENABLE_W0, 1, aspeed_segments_spi, 0x10000000 }, +}; + +static uint64_t aspeed_smc_flash_default_read(void *opaque, hwaddr addr, + unsigned size) +{ + qemu_log_mask(LOG_GUEST_ERROR, "%s: To 0x%" HWADDR_PRIx " of size %u" + PRIx64 "\n", __func__, addr, size); + return 0; +} + +static void aspeed_smc_flash_default_write(void *opaque, hwaddr addr, + uint64_t data, unsigned size) +{ + qemu_log_mask(LOG_GUEST_ERROR, "%s: To 0x%" HWADDR_PRIx " of size %u: 0x%" + PRIx64 "\n", __func__, addr, size, data); +} + +static const MemoryRegionOps aspeed_smc_flash_default_ops = { + .read = aspeed_smc_flash_default_read, + .write = aspeed_smc_flash_default_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static inline int aspeed_smc_flash_mode(const AspeedSMCState *s, int cs) +{ + return s->regs[s->r_ctrl0 + cs] & CTRL_CMD_MODE_MASK; +} + +static inline bool aspeed_smc_is_usermode(const AspeedSMCState *s, int cs) +{ + return aspeed_smc_flash_mode(s, cs) == CTRL_USERMODE; +} + +static inline bool aspeed_smc_is_writable(const AspeedSMCState *s, int cs) +{ + return s->regs[s->r_conf] & (1 << (s->conf_enable_w0 + cs)); +} + +static uint64_t aspeed_smc_flash_read(void *opaque, hwaddr addr, unsigned size) +{ + AspeedSMCFlash *fl = opaque; + const AspeedSMCState *s = fl->controller; + uint64_t ret = 0; + int i; + + if (aspeed_smc_is_usermode(s, fl->id)) { + for (i = 0; i < size; i++) { + ret |= ssi_transfer(s->spi, 0x0) << (8 * i); + } + } else { + qemu_log_mask(LOG_UNIMP, "%s: usermode not implemented\n", + __func__); + ret = -1; + } + + return ret; +} + +static void aspeed_smc_flash_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + AspeedSMCFlash *fl = opaque; + const AspeedSMCState *s = fl->controller; + int i; + + if (!aspeed_smc_is_writable(s, fl->id)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: flash is not writable at 0x%" + HWADDR_PRIx "\n", __func__, addr); + return; + } + + if (!aspeed_smc_is_usermode(s, fl->id)) { + qemu_log_mask(LOG_UNIMP, "%s: usermode not implemented\n", + __func__); + return; + } + + for (i = 0; i < size; i++) { + ssi_transfer(s->spi, (data >> (8 * i)) & 0xff); + } +} + +static const MemoryRegionOps aspeed_smc_flash_ops = { + .read = aspeed_smc_flash_read, + .write = aspeed_smc_flash_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, }; static bool aspeed_smc_is_ce_stop_active(const AspeedSMCState *s, int cs) @@ -237,6 +353,8 @@ static void aspeed_smc_realize(DeviceState *dev, Error **errp) AspeedSMCState *s = ASPEED_SMC(dev); AspeedSMCClass *mc = ASPEED_SMC_GET_CLASS(s); int i; + char name[32]; + hwaddr offset = 0; s->ctrl = mc->ctrl; @@ -270,6 +388,32 @@ static void aspeed_smc_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_smc_ops, s, s->ctrl->name, ASPEED_SMC_R_MAX * 4); sysbus_init_mmio(sbd, &s->mmio); + + /* + * Memory region where flash modules are remapped + */ + snprintf(name, sizeof(name), "%s.flash", s->ctrl->name); + + memory_region_init_io(&s->mmio_flash, OBJECT(s), + &aspeed_smc_flash_default_ops, s, name, + s->ctrl->mapping_window_size); + sysbus_init_mmio(sbd, &s->mmio_flash); + + s->flashes = g_new0(AspeedSMCFlash, s->num_cs); + + for (i = 0; i < s->num_cs; ++i) { + AspeedSMCFlash *fl = &s->flashes[i]; + + snprintf(name, sizeof(name), "%s.%d", s->ctrl->name, i); + + fl->id = i; + fl->controller = s; + fl->size = s->ctrl->segments[i].size; + memory_region_init_io(&fl->mmio, OBJECT(s), &aspeed_smc_flash_ops, + fl, name, fl->size); + memory_region_add_subregion(&s->mmio_flash, offset, &fl->mmio); + offset += fl->size; + } } static const VMStateDescription vmstate_aspeed_smc = { diff --git a/include/hw/ssi/aspeed_smc.h b/include/hw/ssi/aspeed_smc.h index c4a4960cd8..def3b4507e 100644 --- a/include/hw/ssi/aspeed_smc.h +++ b/include/hw/ssi/aspeed_smc.h @@ -27,6 +27,12 @@ #include "hw/ssi/ssi.h" +typedef struct AspeedSegments { + hwaddr addr; + uint32_t size; +} AspeedSegments; + +struct AspeedSMCState; typedef struct AspeedSMCController { const char *name; uint8_t r_conf; @@ -35,8 +41,20 @@ typedef struct AspeedSMCController { uint8_t r_timings; uint8_t conf_enable_w0; uint8_t max_slaves; + const AspeedSegments *segments; + uint32_t mapping_window_size; } AspeedSMCController; +typedef struct AspeedSMCFlash { + const struct AspeedSMCState *controller; + + uint8_t id; + uint32_t size; + + MemoryRegion mmio; + DeviceState *flash; +} AspeedSMCFlash; + #define TYPE_ASPEED_SMC "aspeed.smc" #define ASPEED_SMC(obj) OBJECT_CHECK(AspeedSMCState, (obj), TYPE_ASPEED_SMC) #define ASPEED_SMC_CLASS(klass) \ @@ -57,6 +75,7 @@ typedef struct AspeedSMCState { const AspeedSMCController *ctrl; MemoryRegion mmio; + MemoryRegion mmio_flash; qemu_irq irq; int irqline; @@ -74,6 +93,8 @@ typedef struct AspeedSMCState { uint8_t r_ctrl0; uint8_t r_timings; uint8_t conf_enable_w0; + + AspeedSMCFlash *flashes; } AspeedSMCState; #endif /* ASPEED_SMC_H */ From e1ad9bc405afbd7581831ca1705f39e73c94c5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Mon, 4 Jul 2016 13:06:38 +0100 Subject: [PATCH 23/23] ast2400: create SPI flash slaves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A set of SPI flash slaves is attached under the flash controllers of the palmetto platform. "n25q256a" flash modules are used for the BMC and "mx25l25635e" for the host. These types are common in the OpenPower ecosystem. Signed-off-by: Cédric Le Goater Message-id: 1467138270-32481-9-git-send-email-clg@kaod.org Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/arm/palmetto-bmc.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/hw/arm/palmetto-bmc.c b/hw/arm/palmetto-bmc.c index b8eed21348..54e29a865d 100644 --- a/hw/arm/palmetto-bmc.c +++ b/hw/arm/palmetto-bmc.c @@ -18,6 +18,8 @@ #include "hw/arm/ast2400.h" #include "hw/boards.h" #include "qemu/log.h" +#include "sysemu/block-backend.h" +#include "sysemu/blockdev.h" static struct arm_boot_info palmetto_bmc_binfo = { .loader_start = AST2400_SDRAM_BASE, @@ -30,6 +32,32 @@ typedef struct PalmettoBMCState { MemoryRegion ram; } PalmettoBMCState; +static void palmetto_bmc_init_flashes(AspeedSMCState *s, const char *flashtype, + Error **errp) +{ + int i ; + + for (i = 0; i < s->num_cs; ++i) { + AspeedSMCFlash *fl = &s->flashes[i]; + DriveInfo *dinfo = drive_get_next(IF_MTD); + qemu_irq cs_line; + + /* + * FIXME: check that we are not using a flash module exceeding + * the controller segment size + */ + fl->flash = ssi_create_slave_no_init(s->spi, flashtype); + if (dinfo) { + qdev_prop_set_drive(fl->flash, "drive", blk_by_legacy_dinfo(dinfo), + errp); + } + qdev_init_nofail(fl->flash); + + cs_line = qdev_get_gpio_in_named(fl->flash, SSI_GPIO_CS, 0); + sysbus_connect_irq(SYS_BUS_DEVICE(s), i + 1, cs_line); + } +} + static void palmetto_bmc_init(MachineState *machine) { PalmettoBMCState *bmc; @@ -49,6 +77,9 @@ static void palmetto_bmc_init(MachineState *machine) object_property_set_bool(OBJECT(&bmc->soc), true, "realized", &error_abort); + palmetto_bmc_init_flashes(&bmc->soc.smc, "n25q256a", &error_abort); + palmetto_bmc_init_flashes(&bmc->soc.spi, "mx25l25635e", &error_abort); + palmetto_bmc_binfo.kernel_filename = machine->kernel_filename; palmetto_bmc_binfo.initrd_filename = machine->initrd_filename; palmetto_bmc_binfo.kernel_cmdline = machine->kernel_cmdline;