diff options
Diffstat (limited to 'hw')
37 files changed, 3213 insertions, 416 deletions
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 5ee6f7da5b..2794e086d6 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -19,3 +19,4 @@ obj-$(CONFIG_FSL_IMX31) += fsl-imx31.o kzm.o obj-$(CONFIG_FSL_IMX6) += fsl-imx6.o sabrelite.o obj-$(CONFIG_ASPEED_SOC) += aspeed_soc.o aspeed.o obj-$(CONFIG_MPS2) += mps2.o +obj-$(CONFIG_MSF2) += msf2-soc.o msf2-som.o diff --git a/hw/arm/msf2-soc.c b/hw/arm/msf2-soc.c new file mode 100644 index 0000000000..6f97fa9fe3 --- /dev/null +++ b/hw/arm/msf2-soc.c @@ -0,0 +1,238 @@ +/* + * SmartFusion2 SoC emulation. + * + * Copyright (c) 2017 Subbaraya Sundeep <sundeep.lkml@gmail.com> + * + * 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 "qapi/error.h" +#include "qemu-common.h" +#include "hw/arm/arm.h" +#include "exec/address-spaces.h" +#include "hw/char/serial.h" +#include "hw/boards.h" +#include "sysemu/block-backend.h" +#include "qemu/cutils.h" +#include "hw/arm/msf2-soc.h" +#include "hw/misc/unimp.h" + +#define MSF2_TIMER_BASE 0x40004000 +#define MSF2_SYSREG_BASE 0x40038000 + +#define ENVM_BASE_ADDRESS 0x60000000 + +#define SRAM_BASE_ADDRESS 0x20000000 + +#define MSF2_ENVM_MAX_SIZE (512 * K_BYTE) + +/* + * eSRAM max size is 80k without SECDED(Single error correction and + * dual error detection) feature and 64k with SECDED. + * We do not support SECDED now. + */ +#define MSF2_ESRAM_MAX_SIZE (80 * K_BYTE) + +static const uint32_t spi_addr[MSF2_NUM_SPIS] = { 0x40001000 , 0x40011000 }; +static const uint32_t uart_addr[MSF2_NUM_UARTS] = { 0x40000000 , 0x40010000 }; + +static const int spi_irq[MSF2_NUM_SPIS] = { 2, 3 }; +static const int uart_irq[MSF2_NUM_UARTS] = { 10, 11 }; +static const int timer_irq[MSF2_NUM_TIMERS] = { 14, 15 }; + +static void m2sxxx_soc_initfn(Object *obj) +{ + MSF2State *s = MSF2_SOC(obj); + int i; + + object_initialize(&s->armv7m, sizeof(s->armv7m), TYPE_ARMV7M); + qdev_set_parent_bus(DEVICE(&s->armv7m), sysbus_get_default()); + + object_initialize(&s->sysreg, sizeof(s->sysreg), TYPE_MSF2_SYSREG); + qdev_set_parent_bus(DEVICE(&s->sysreg), sysbus_get_default()); + + object_initialize(&s->timer, sizeof(s->timer), TYPE_MSS_TIMER); + qdev_set_parent_bus(DEVICE(&s->timer), sysbus_get_default()); + + for (i = 0; i < MSF2_NUM_SPIS; i++) { + object_initialize(&s->spi[i], sizeof(s->spi[i]), + TYPE_MSS_SPI); + qdev_set_parent_bus(DEVICE(&s->spi[i]), sysbus_get_default()); + } +} + +static void m2sxxx_soc_realize(DeviceState *dev_soc, Error **errp) +{ + MSF2State *s = MSF2_SOC(dev_soc); + DeviceState *dev, *armv7m; + SysBusDevice *busdev; + Error *err = NULL; + int i; + + MemoryRegion *system_memory = get_system_memory(); + MemoryRegion *nvm = g_new(MemoryRegion, 1); + MemoryRegion *nvm_alias = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + + memory_region_init_rom(nvm, NULL, "MSF2.eNVM", s->envm_size, + &error_fatal); + /* + * On power-on, the eNVM region 0x60000000 is automatically + * remapped to the Cortex-M3 processor executable region + * start address (0x0). We do not support remapping other eNVM, + * eSRAM and DDR regions by guest(via Sysreg) currently. + */ + memory_region_init_alias(nvm_alias, NULL, "MSF2.eNVM", + nvm, 0, s->envm_size); + + memory_region_add_subregion(system_memory, ENVM_BASE_ADDRESS, nvm); + memory_region_add_subregion(system_memory, 0, nvm_alias); + + memory_region_init_ram(sram, NULL, "MSF2.eSRAM", s->esram_size, + &error_fatal); + memory_region_add_subregion(system_memory, SRAM_BASE_ADDRESS, sram); + + armv7m = DEVICE(&s->armv7m); + qdev_prop_set_uint32(armv7m, "num-irq", 81); + qdev_prop_set_string(armv7m, "cpu-type", s->cpu_type); + object_property_set_link(OBJECT(&s->armv7m), OBJECT(get_system_memory()), + "memory", &error_abort); + object_property_set_bool(OBJECT(&s->armv7m), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + + if (!s->m3clk) { + error_setg(errp, "Invalid m3clk value"); + error_append_hint(errp, "m3clk can not be zero\n"); + return; + } + system_clock_scale = NANOSECONDS_PER_SECOND / s->m3clk; + + for (i = 0; i < MSF2_NUM_UARTS; i++) { + if (serial_hds[i]) { + serial_mm_init(get_system_memory(), uart_addr[i], 2, + qdev_get_gpio_in(armv7m, uart_irq[i]), + 115200, serial_hds[i], DEVICE_NATIVE_ENDIAN); + } + } + + dev = DEVICE(&s->timer); + /* APB0 clock is the timer input clock */ + qdev_prop_set_uint32(dev, "clock-frequency", s->m3clk / s->apb0div); + object_property_set_bool(OBJECT(&s->timer), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, MSF2_TIMER_BASE); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(armv7m, timer_irq[0])); + sysbus_connect_irq(busdev, 1, + qdev_get_gpio_in(armv7m, timer_irq[1])); + + dev = DEVICE(&s->sysreg); + qdev_prop_set_uint32(dev, "apb0divisor", s->apb0div); + qdev_prop_set_uint32(dev, "apb1divisor", s->apb1div); + object_property_set_bool(OBJECT(&s->sysreg), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, MSF2_SYSREG_BASE); + + for (i = 0; i < MSF2_NUM_SPIS; i++) { + gchar *bus_name; + + object_property_set_bool(OBJECT(&s->spi[i]), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi[i]), 0, spi_addr[i]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi[i]), 0, + qdev_get_gpio_in(armv7m, spi_irq[i])); + + /* Alias controller SPI bus to the SoC itself */ + bus_name = g_strdup_printf("spi%d", i); + object_property_add_alias(OBJECT(s), bus_name, + OBJECT(&s->spi[i]), "spi", + &error_abort); + g_free(bus_name); + } + + /* Below devices are not modelled yet. */ + create_unimplemented_device("i2c_0", 0x40002000, 0x1000); + create_unimplemented_device("dma", 0x40003000, 0x1000); + create_unimplemented_device("watchdog", 0x40005000, 0x1000); + create_unimplemented_device("i2c_1", 0x40012000, 0x1000); + create_unimplemented_device("gpio", 0x40013000, 0x1000); + create_unimplemented_device("hs-dma", 0x40014000, 0x1000); + create_unimplemented_device("can", 0x40015000, 0x1000); + create_unimplemented_device("rtc", 0x40017000, 0x1000); + create_unimplemented_device("apb_config", 0x40020000, 0x10000); + create_unimplemented_device("emac", 0x40041000, 0x1000); + create_unimplemented_device("usb", 0x40043000, 0x1000); +} + +static Property m2sxxx_soc_properties[] = { + /* + * part name specifies the type of SmartFusion2 device variant(this + * property is for information purpose only. + */ + DEFINE_PROP_STRING("cpu-type", MSF2State, cpu_type), + DEFINE_PROP_STRING("part-name", MSF2State, part_name), + DEFINE_PROP_UINT64("eNVM-size", MSF2State, envm_size, MSF2_ENVM_MAX_SIZE), + DEFINE_PROP_UINT64("eSRAM-size", MSF2State, esram_size, + MSF2_ESRAM_MAX_SIZE), + /* Libero GUI shows 100Mhz as default for clocks */ + DEFINE_PROP_UINT32("m3clk", MSF2State, m3clk, 100 * 1000000), + /* default divisors in Libero GUI */ + DEFINE_PROP_UINT8("apb0div", MSF2State, apb0div, 2), + DEFINE_PROP_UINT8("apb1div", MSF2State, apb1div, 2), + DEFINE_PROP_END_OF_LIST(), +}; + +static void m2sxxx_soc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = m2sxxx_soc_realize; + dc->props = m2sxxx_soc_properties; +} + +static const TypeInfo m2sxxx_soc_info = { + .name = TYPE_MSF2_SOC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MSF2State), + .instance_init = m2sxxx_soc_initfn, + .class_init = m2sxxx_soc_class_init, +}; + +static void m2sxxx_soc_types(void) +{ + type_register_static(&m2sxxx_soc_info); +} + +type_init(m2sxxx_soc_types) diff --git a/hw/arm/msf2-som.c b/hw/arm/msf2-som.c new file mode 100644 index 0000000000..0795a3a3a1 --- /dev/null +++ b/hw/arm/msf2-som.c @@ -0,0 +1,105 @@ +/* + * SmartFusion2 SOM starter kit(from Emcraft) emulation. + * + * Copyright (c) 2017 Subbaraya Sundeep <sundeep.lkml@gmail.com> + * + * 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 "qapi/error.h" +#include "qemu/error-report.h" +#include "hw/boards.h" +#include "hw/arm/arm.h" +#include "exec/address-spaces.h" +#include "qemu/cutils.h" +#include "hw/arm/msf2-soc.h" +#include "cpu.h" + +#define DDR_BASE_ADDRESS 0xA0000000 +#define DDR_SIZE (64 * M_BYTE) + +#define M2S010_ENVM_SIZE (256 * K_BYTE) +#define M2S010_ESRAM_SIZE (64 * K_BYTE) + +static void emcraft_sf2_s2s010_init(MachineState *machine) +{ + DeviceState *dev; + DeviceState *spi_flash; + MSF2State *soc; + MachineClass *mc = MACHINE_GET_CLASS(machine); + DriveInfo *dinfo = drive_get_next(IF_MTD); + qemu_irq cs_line; + SSIBus *spi_bus; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ddr = g_new(MemoryRegion, 1); + + if (strcmp(machine->cpu_type, mc->default_cpu_type) != 0) { + error_report("This board can only be used with CPU %s", + mc->default_cpu_type); + } + + memory_region_init_ram(ddr, NULL, "ddr-ram", DDR_SIZE, + &error_fatal); + memory_region_add_subregion(sysmem, DDR_BASE_ADDRESS, ddr); + + dev = qdev_create(NULL, TYPE_MSF2_SOC); + qdev_prop_set_string(dev, "part-name", "M2S010"); + qdev_prop_set_string(dev, "cpu-type", mc->default_cpu_type); + + qdev_prop_set_uint64(dev, "eNVM-size", M2S010_ENVM_SIZE); + qdev_prop_set_uint64(dev, "eSRAM-size", M2S010_ESRAM_SIZE); + + /* + * CPU clock and peripheral clocks(APB0, APB1)are configurable + * in Libero. CPU clock is divided by APB0 and APB1 divisors for + * peripherals. Emcraft's SoM kit comes with these settings by default. + */ + qdev_prop_set_uint32(dev, "m3clk", 142 * 1000000); + qdev_prop_set_uint32(dev, "apb0div", 2); + qdev_prop_set_uint32(dev, "apb1div", 2); + + object_property_set_bool(OBJECT(dev), true, "realized", &error_fatal); + + soc = MSF2_SOC(dev); + + /* Attach SPI flash to SPI0 controller */ + spi_bus = (SSIBus *)qdev_get_child_bus(dev, "spi0"); + spi_flash = ssi_create_slave_no_init(spi_bus, "s25sl12801"); + qdev_prop_set_uint8(spi_flash, "spansion-cr2nv", 1); + if (dinfo) { + qdev_prop_set_drive(spi_flash, "drive", blk_by_legacy_dinfo(dinfo), + &error_fatal); + } + qdev_init_nofail(spi_flash); + cs_line = qdev_get_gpio_in_named(spi_flash, SSI_GPIO_CS, 0); + sysbus_connect_irq(SYS_BUS_DEVICE(&soc->spi[0]), 1, cs_line); + + armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, + soc->envm_size); +} + +static void emcraft_sf2_machine_init(MachineClass *mc) +{ + mc->desc = "SmartFusion2 SOM kit from Emcraft (M2S010)"; + mc->init = emcraft_sf2_s2s010_init; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3"); +} + +DEFINE_MACHINE("emcraft-sf2", emcraft_sf2_machine_init) diff --git a/hw/arm/omap2.c b/hw/arm/omap2.c index 3f6076ede8..f5b148881c 100644 --- a/hw/arm/omap2.c +++ b/hw/arm/omap2.c @@ -2087,19 +2087,44 @@ static void omap_sysctl_write(void *opaque, hwaddr addr, } } +static uint64_t omap_sysctl_readfn(void *opaque, hwaddr addr, + unsigned size) +{ + switch (size) { + case 1: + return omap_sysctl_read8(opaque, addr); + case 2: + return omap_badwidth_read32(opaque, addr); /* TODO */ + case 4: + return omap_sysctl_read(opaque, addr); + default: + g_assert_not_reached(); + } +} + +static void omap_sysctl_writefn(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + switch (size) { + case 1: + omap_sysctl_write8(opaque, addr, value); + break; + case 2: + omap_badwidth_write32(opaque, addr, value); /* TODO */ + break; + case 4: + omap_sysctl_write(opaque, addr, value); + break; + default: + g_assert_not_reached(); + } +} + static const MemoryRegionOps omap_sysctl_ops = { - .old_mmio = { - .read = { - omap_sysctl_read8, - omap_badwidth_read32, /* TODO */ - omap_sysctl_read, - }, - .write = { - omap_sysctl_write8, - omap_badwidth_write32, /* TODO */ - omap_sysctl_write, - }, - }, + .read = omap_sysctl_readfn, + .write = omap_sysctl_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, .endianness = DEVICE_NATIVE_ENDIAN, }; diff --git a/hw/arm/palm.c b/hw/arm/palm.c index b8753e2b5c..a1f55d79b4 100644 --- a/hw/arm/palm.c +++ b/hw/arm/palm.c @@ -31,26 +31,16 @@ #include "exec/address-spaces.h" #include "cpu.h" -static uint32_t static_readb(void *opaque, hwaddr offset) +static uint64_t static_read(void *opaque, hwaddr offset, unsigned size) { - uint32_t *val = (uint32_t *) opaque; - return *val >> ((offset & 3) << 3); -} + uint32_t *val = (uint32_t *)opaque; + uint32_t sizemask = 7 >> size; -static uint32_t static_readh(void *opaque, hwaddr offset) -{ - uint32_t *val = (uint32_t *) opaque; - return *val >> ((offset & 1) << 3); -} - -static uint32_t static_readw(void *opaque, hwaddr offset) -{ - uint32_t *val = (uint32_t *) opaque; - return *val >> ((offset & 0) << 3); + return *val >> ((offset & sizemask) << 3); } -static void static_write(void *opaque, hwaddr offset, - uint32_t value) +static void static_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) { #ifdef SPY printf("%s: value %08lx written at " PA_FMT "\n", @@ -59,10 +49,10 @@ static void static_write(void *opaque, hwaddr offset, } static const MemoryRegionOps static_ops = { - .old_mmio = { - .read = { static_readb, static_readh, static_readw, }, - .write = { static_write, static_write, static_write, }, - }, + .read = static_read, + .write = static_write, + .valid.min_access_size = 1, + .valid.max_access_size = 4, .endianness = DEVICE_NATIVE_ENDIAN, }; diff --git a/hw/block/xen_disk.c b/hw/block/xen_disk.c index d42ed7070d..536e2ee735 100644 --- a/hw/block/xen_disk.c +++ b/hw/block/xen_disk.c @@ -1232,7 +1232,7 @@ static int blk_connect(struct XenDevice *xendev) return -1; } - domids = g_malloc0_n(blkdev->nr_ring_ref, sizeof(uint32_t)); + domids = g_new0(uint32_t, blkdev->nr_ring_ref); for (i = 0; i < blkdev->nr_ring_ref; i++) { domids[i] = blkdev->xendev.dom; } diff --git a/hw/gpio/omap_gpio.c b/hw/gpio/omap_gpio.c index 1df394eb12..17891e2d0f 100644 --- a/hw/gpio/omap_gpio.c +++ b/hw/gpio/omap_gpio.c @@ -525,17 +525,23 @@ static void omap2_gpio_module_write(void *opaque, hwaddr addr, } } -static uint32_t omap2_gpio_module_readp(void *opaque, hwaddr addr) +static uint64_t omap2_gpio_module_readp(void *opaque, hwaddr addr, + unsigned size) { return omap2_gpio_module_read(opaque, addr & ~3) >> ((addr & 3) << 3); } static void omap2_gpio_module_writep(void *opaque, hwaddr addr, - uint32_t value) + uint64_t value, unsigned size) { uint32_t cur = 0; uint32_t mask = 0xffff; + if (size == 4) { + omap2_gpio_module_write(opaque, addr, value); + return; + } + switch (addr & ~3) { case 0x00: /* GPIO_REVISION */ case 0x14: /* GPIO_SYSSTATUS */ @@ -581,18 +587,10 @@ static void omap2_gpio_module_writep(void *opaque, hwaddr addr, } static const MemoryRegionOps omap2_gpio_module_ops = { - .old_mmio = { - .read = { - omap2_gpio_module_readp, - omap2_gpio_module_readp, - omap2_gpio_module_read, - }, - .write = { - omap2_gpio_module_writep, - omap2_gpio_module_writep, - omap2_gpio_module_write, - }, - }, + .read = omap2_gpio_module_readp, + .write = omap2_gpio_module_writep, + .valid.min_access_size = 1, + .valid.max_access_size = 4, .endianness = DEVICE_NATIVE_ENDIAN, }; diff --git a/hw/i2c/omap_i2c.c b/hw/i2c/omap_i2c.c index f6e80bee25..12264ee0f5 100644 --- a/hw/i2c/omap_i2c.c +++ b/hw/i2c/omap_i2c.c @@ -430,19 +430,39 @@ static void omap_i2c_writeb(void *opaque, hwaddr addr, } } +static uint64_t omap_i2c_readfn(void *opaque, hwaddr addr, + unsigned size) +{ + switch (size) { + case 2: + return omap_i2c_read(opaque, addr); + default: + return omap_badwidth_read16(opaque, addr); + } +} + +static void omap_i2c_writefn(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + switch (size) { + case 1: + /* Only the last fifo write can be 8 bit. */ + omap_i2c_writeb(opaque, addr, value); + break; + case 2: + omap_i2c_write(opaque, addr, value); + break; + default: + omap_badwidth_write16(opaque, addr, value); + break; + } +} + static const MemoryRegionOps omap_i2c_ops = { - .old_mmio = { - .read = { - omap_badwidth_read16, - omap_i2c_read, - omap_badwidth_read16, - }, - .write = { - omap_i2c_writeb, /* Only the last fifo write can be 8 bit. */ - omap_i2c_write, - omap_badwidth_write16, - }, - }, + .read = omap_i2c_readfn, + .write = omap_i2c_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, .endianness = DEVICE_NATIVE_ENDIAN, }; diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c index 24c65dfab3..32d1296a64 100644 --- a/hw/ide/ahci.c +++ b/hw/ide/ahci.c @@ -184,7 +184,7 @@ static void ahci_check_irq(AHCIState *s) static void ahci_trigger_irq(AHCIState *s, AHCIDevice *d, enum AHCIPortIRQ irqbit) { - g_assert(irqbit >= 0 && irqbit < 32); + g_assert((unsigned)irqbit < 32); uint32_t irq = 1U << irqbit; uint32_t irqstat = d->port_regs.irq_stat | irq; diff --git a/hw/ide/core.c b/hw/ide/core.c index a19bd9011c..d63eb4a72e 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -68,7 +68,7 @@ const char *IDE_DMA_CMD_lookup[IDE_DMA__COUNT] = { static const char *IDE_DMA_CMD_str(enum ide_dma_cmd enval) { - if (enval >= IDE_DMA__BEGIN && enval < IDE_DMA__COUNT) { + if ((unsigned)enval < IDE_DMA__COUNT) { return IDE_DMA_CMD_lookup[enval]; } return "DMA UNKNOWN CMD"; diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c index d3e20561c7..d90d8d0784 100644 --- a/hw/intc/armv7m_nvic.c +++ b/hw/intc/armv7m_nvic.c @@ -47,13 +47,15 @@ * For historical reasons QEMU tends to use "interrupt" and * "exception" more or less interchangeably. */ -#define NVIC_FIRST_IRQ 16 +#define NVIC_FIRST_IRQ NVIC_INTERNAL_VECTORS #define NVIC_MAX_IRQ (NVIC_MAX_VECTORS - NVIC_FIRST_IRQ) /* Effective running priority of the CPU when no exception is active * (higher than the highest possible priority value) */ #define NVIC_NOEXC_PRIO 0x100 +/* Maximum priority of non-secure exceptions when AIRCR.PRIS is set */ +#define NVIC_NS_PRIO_LIMIT 0x80 static const uint8_t nvic_id[] = { 0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1 @@ -61,10 +63,10 @@ static const uint8_t nvic_id[] = { static int nvic_pending_prio(NVICState *s) { - /* return the priority of the current pending interrupt, + /* return the group priority of the current pending interrupt, * or NVIC_NOEXC_PRIO if no interrupt is pending */ - return s->vectpending ? s->vectors[s->vectpending].prio : NVIC_NOEXC_PRIO; + return s->vectpending_prio; } /* Return the value of the ISCR RETTOBASE bit: @@ -84,9 +86,12 @@ static int nvic_pending_prio(NVICState *s) static bool nvic_rettobase(NVICState *s) { int irq, nhand = 0; + bool check_sec = arm_feature(&s->cpu->env, ARM_FEATURE_M_SECURITY); for (irq = ARMV7M_EXCP_RESET; irq < s->num_irq; irq++) { - if (s->vectors[irq].active) { + if (s->vectors[irq].active || + (check_sec && irq < NVIC_INTERNAL_VECTORS && + s->sec_vectors[irq].active)) { nhand++; if (nhand == 2) { return 0; @@ -123,13 +128,139 @@ static bool nvic_isrpending(NVICState *s) return false; } +static bool exc_is_banked(int exc) +{ + /* Return true if this is one of the limited set of exceptions which + * are banked (and thus have state in sec_vectors[]) + */ + return exc == ARMV7M_EXCP_HARD || + exc == ARMV7M_EXCP_MEM || + exc == ARMV7M_EXCP_USAGE || + exc == ARMV7M_EXCP_SVC || + exc == ARMV7M_EXCP_PENDSV || + exc == ARMV7M_EXCP_SYSTICK; +} + /* Return a mask word which clears the subpriority bits from * a priority value for an M-profile exception, leaving only * the group priority. */ -static inline uint32_t nvic_gprio_mask(NVICState *s) +static inline uint32_t nvic_gprio_mask(NVICState *s, bool secure) +{ + return ~0U << (s->prigroup[secure] + 1); +} + +static bool exc_targets_secure(NVICState *s, int exc) +{ + /* Return true if this non-banked exception targets Secure state. */ + if (!arm_feature(&s->cpu->env, ARM_FEATURE_M_SECURITY)) { + return false; + } + + if (exc >= NVIC_FIRST_IRQ) { + return !s->itns[exc]; + } + + /* Function shouldn't be called for banked exceptions. */ + assert(!exc_is_banked(exc)); + + switch (exc) { + case ARMV7M_EXCP_NMI: + case ARMV7M_EXCP_BUS: + return !(s->cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK); + case ARMV7M_EXCP_SECURE: + return true; + case ARMV7M_EXCP_DEBUG: + /* TODO: controlled by DEMCR.SDME, which we don't yet implement */ + return false; + default: + /* reset, and reserved (unused) low exception numbers. + * We'll get called by code that loops through all the exception + * numbers, but it doesn't matter what we return here as these + * non-existent exceptions will never be pended or active. + */ + return true; + } +} + +static int exc_group_prio(NVICState *s, int rawprio, bool targets_secure) +{ + /* Return the group priority for this exception, given its raw + * (group-and-subgroup) priority value and whether it is targeting + * secure state or not. + */ + if (rawprio < 0) { + return rawprio; + } + rawprio &= nvic_gprio_mask(s, targets_secure); + /* AIRCR.PRIS causes us to squash all NS priorities into the + * lower half of the total range + */ + if (!targets_secure && + (s->cpu->env.v7m.aircr & R_V7M_AIRCR_PRIS_MASK)) { + rawprio = (rawprio >> 1) + NVIC_NS_PRIO_LIMIT; + } + return rawprio; +} + +/* Recompute vectpending and exception_prio for a CPU which implements + * the Security extension + */ +static void nvic_recompute_state_secure(NVICState *s) { - return ~0U << (s->prigroup + 1); + int i, bank; + int pend_prio = NVIC_NOEXC_PRIO; + int active_prio = NVIC_NOEXC_PRIO; + int pend_irq = 0; + bool pending_is_s_banked = false; + + /* R_CQRV: precedence is by: + * - lowest group priority; if both the same then + * - lowest subpriority; if both the same then + * - lowest exception number; if both the same (ie banked) then + * - secure exception takes precedence + * Compare pseudocode RawExecutionPriority. + * Annoyingly, now we have two prigroup values (for S and NS) + * we can't do the loop comparison on raw priority values. + */ + for (i = 1; i < s->num_irq; i++) { + for (bank = M_REG_S; bank >= M_REG_NS; bank--) { + VecInfo *vec; + int prio; + bool targets_secure; + + if (bank == M_REG_S) { + if (!exc_is_banked(i)) { + continue; + } + vec = &s->sec_vectors[i]; + targets_secure = true; + } else { + vec = &s->vectors[i]; + targets_secure = !exc_is_banked(i) && exc_targets_secure(s, i); + } + + prio = exc_group_prio(s, vec->prio, targets_secure); + if (vec->enabled && vec->pending && prio < pend_prio) { + pend_prio = prio; + pend_irq = i; + pending_is_s_banked = (bank == M_REG_S); + } + if (vec->active && prio < active_prio) { + active_prio = prio; + } + } + } + + s->vectpending_is_s_banked = pending_is_s_banked; + s->vectpending = pend_irq; + s->vectpending_prio = pend_prio; + s->exception_prio = active_prio; + + trace_nvic_recompute_state_secure(s->vectpending, + s->vectpending_is_s_banked, + s->vectpending_prio, + s->exception_prio); } /* Recompute vectpending and exception_prio */ @@ -140,6 +271,18 @@ static void nvic_recompute_state(NVICState *s) int active_prio = NVIC_NOEXC_PRIO; int pend_irq = 0; + /* In theory we could write one function that handled both + * the "security extension present" and "not present"; however + * the security related changes significantly complicate the + * recomputation just by themselves and mixing both cases together + * would be even worse, so we retain a separate non-secure-only + * version for CPUs which don't implement the security extension. + */ + if (arm_feature(&s->cpu->env, ARM_FEATURE_M_SECURITY)) { + nvic_recompute_state_secure(s); + return; + } + for (i = 1; i < s->num_irq; i++) { VecInfo *vec = &s->vectors[i]; @@ -153,13 +296,20 @@ static void nvic_recompute_state(NVICState *s) } if (active_prio > 0) { - active_prio &= nvic_gprio_mask(s); + active_prio &= nvic_gprio_mask(s, false); + } + + if (pend_prio > 0) { + pend_prio &= nvic_gprio_mask(s, false); } s->vectpending = pend_irq; + s->vectpending_prio = pend_prio; s->exception_prio = active_prio; - trace_nvic_recompute_state(s->vectpending, s->exception_prio); + trace_nvic_recompute_state(s->vectpending, + s->vectpending_prio, + s->exception_prio); } /* Return the current execution priority of the CPU @@ -169,21 +319,84 @@ static void nvic_recompute_state(NVICState *s) static inline int nvic_exec_prio(NVICState *s) { CPUARMState *env = &s->cpu->env; - int running; + int running = NVIC_NOEXC_PRIO; + + if (env->v7m.basepri[M_REG_NS] > 0) { + running = exc_group_prio(s, env->v7m.basepri[M_REG_NS], M_REG_NS); + } + + if (env->v7m.basepri[M_REG_S] > 0) { + int basepri = exc_group_prio(s, env->v7m.basepri[M_REG_S], M_REG_S); + if (running > basepri) { + running = basepri; + } + } - if (env->v7m.faultmask[env->v7m.secure]) { - running = -1; - } else if (env->v7m.primask[env->v7m.secure]) { + if (env->v7m.primask[M_REG_NS]) { + if (env->v7m.aircr & R_V7M_AIRCR_PRIS_MASK) { + if (running > NVIC_NS_PRIO_LIMIT) { + running = NVIC_NS_PRIO_LIMIT; + } + } else { + running = 0; + } + } + + if (env->v7m.primask[M_REG_S]) { running = 0; - } else if (env->v7m.basepri[env->v7m.secure] > 0) { - running = env->v7m.basepri[env->v7m.secure] & nvic_gprio_mask(s); - } else { - running = NVIC_NOEXC_PRIO; /* lower than any possible priority */ } + + if (env->v7m.faultmask[M_REG_NS]) { + if (env->v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK) { + running = -1; + } else { + if (env->v7m.aircr & R_V7M_AIRCR_PRIS_MASK) { + if (running > NVIC_NS_PRIO_LIMIT) { + running = NVIC_NS_PRIO_LIMIT; + } + } else { + running = 0; + } + } + } + + if (env->v7m.faultmask[M_REG_S]) { + running = (env->v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK) ? -3 : -1; + } + /* consider priority of active handler */ return MIN(running, s->exception_prio); } +bool armv7m_nvic_neg_prio_requested(void *opaque, bool secure) +{ + /* Return true if the requested execution priority is negative + * for the specified security state, ie that security state + * has an active NMI or HardFault or has set its FAULTMASK. + * Note that this is not the same as whether the execution + * priority is actually negative (for instance AIRCR.PRIS may + * mean we don't allow FAULTMASK_NS to actually make the execution + * priority negative). Compare pseudocode IsReqExcPriNeg(). + */ + NVICState *s = opaque; + + if (s->cpu->env.v7m.faultmask[secure]) { + return true; + } + + if (secure ? s->sec_vectors[ARMV7M_EXCP_HARD].active : + s->vectors[ARMV7M_EXCP_HARD].active) { + return true; + } + + if (s->vectors[ARMV7M_EXCP_NMI].active && + exc_targets_secure(s, ARMV7M_EXCP_NMI) == secure) { + return true; + } + + return false; +} + bool armv7m_nvic_can_take_pending_exception(void *opaque) { NVICState *s = opaque; @@ -198,15 +411,40 @@ int armv7m_nvic_raw_execution_priority(void *opaque) return s->exception_prio; } -/* caller must call nvic_irq_update() after this */ -static void set_prio(NVICState *s, unsigned irq, uint8_t prio) +/* caller must call nvic_irq_update() after this. + * secure indicates the bank to use for banked exceptions (we assert if + * we are passed secure=true for a non-banked exception). + */ +static void set_prio(NVICState *s, unsigned irq, bool secure, uint8_t prio) { assert(irq > ARMV7M_EXCP_NMI); /* only use for configurable prios */ assert(irq < s->num_irq); - s->vectors[irq].prio = prio; + if (secure) { + assert(exc_is_banked(irq)); + s->sec_vectors[irq].prio = prio; + } else { + s->vectors[irq].prio = prio; + } + + trace_nvic_set_prio(irq, secure, prio); +} + +/* Return the current raw priority register value. + * secure indicates the bank to use for banked exceptions (we assert if + * we are passed secure=true for a non-banked exception). + */ +static int get_prio(NVICState *s, unsigned irq, bool secure) +{ + assert(irq > ARMV7M_EXCP_NMI); /* only use for configurable prios */ + assert(irq < s->num_irq); - trace_nvic_set_prio(irq, prio); + if (secure) { + assert(exc_is_banked(irq)); + return s->sec_vectors[irq].prio; + } else { + return s->vectors[irq].prio; + } } /* Recompute state and assert irq line accordingly. @@ -233,31 +471,50 @@ static void nvic_irq_update(NVICState *s) qemu_set_irq(s->excpout, lvl); } -static void armv7m_nvic_clear_pending(void *opaque, int irq) +/** + * armv7m_nvic_clear_pending: mark the specified exception as not pending + * @opaque: the NVIC + * @irq: the exception number to mark as not pending + * @secure: false for non-banked exceptions or for the nonsecure + * version of a banked exception, true for the secure version of a banked + * exception. + * + * Marks the specified exception as not pending. Note that we will assert() + * if @secure is true and @irq does not specify one of the fixed set + * of architecturally banked exceptions. + */ +static void armv7m_nvic_clear_pending(void *opaque, int irq, bool secure) { NVICState *s = (NVICState *)opaque; VecInfo *vec; assert(irq > ARMV7M_EXCP_RESET && irq < s->num_irq); - vec = &s->vectors[irq]; - trace_nvic_clear_pending(irq, vec->enabled, vec->prio); + if (secure) { + assert(exc_is_banked(irq)); + vec = &s->sec_vectors[irq]; + } else { + vec = &s->vectors[irq]; + } + trace_nvic_clear_pending(irq, secure, vec->enabled, vec->prio); if (vec->pending) { vec->pending = 0; nvic_irq_update(s); } } -void armv7m_nvic_set_pending(void *opaque, int irq) +void armv7m_nvic_set_pending(void *opaque, int irq, bool secure) { NVICState *s = (NVICState *)opaque; + bool banked = exc_is_banked(irq); VecInfo *vec; assert(irq > ARMV7M_EXCP_RESET && irq < s->num_irq); + assert(!secure || banked); - vec = &s->vectors[irq]; - trace_nvic_set_pending(irq, vec->enabled, vec->prio); + vec = (banked && secure) ? &s->sec_vectors[irq] : &s->vectors[irq]; + trace_nvic_set_pending(irq, secure, vec->enabled, vec->prio); if (irq >= ARMV7M_EXCP_HARD && irq < ARMV7M_EXCP_PENDSV) { /* If a synchronous exception is pending then it may be @@ -283,7 +540,7 @@ void armv7m_nvic_set_pending(void *opaque, int irq) int running = nvic_exec_prio(s); bool escalate = false; - if (vec->prio >= running) { + if (exc_group_prio(s, vec->prio, secure) >= running) { trace_nvic_escalate_prio(irq, vec->prio, running); escalate = true; } else if (!vec->enabled) { @@ -292,8 +549,22 @@ void armv7m_nvic_set_pending(void *opaque, int irq) } if (escalate) { - if (running < 0) { - /* We want to escalate to HardFault but we can't take a + + /* We need to escalate this exception to a synchronous HardFault. + * If BFHFNMINS is set then we escalate to the banked HF for + * the target security state of the original exception; otherwise + * we take a Secure HardFault. + */ + irq = ARMV7M_EXCP_HARD; + if (arm_feature(&s->cpu->env, ARM_FEATURE_M_SECURITY) && + (secure || + !(s->cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK))) { + vec = &s->sec_vectors[irq]; + } else { + vec = &s->vectors[irq]; + } + if (running <= vec->prio) { + /* We want to escalate to HardFault but we can't take the * synchronous HardFault at this point either. This is a * Lockup condition due to a guest bug. We don't model * Lockup, so report via cpu_abort() instead. @@ -303,9 +574,7 @@ void armv7m_nvic_set_pending(void *opaque, int irq) "(current priority %d)\n", irq, running); } - /* We can do the escalation, so we take HardFault instead */ - irq = ARMV7M_EXCP_HARD; - vec = &s->vectors[irq]; + /* HF may be banked but there is only one shared HFSR */ s->cpu->env.v7m.hfsr |= R_V7M_HFSR_FORCED_MASK; } } @@ -317,29 +586,32 @@ void armv7m_nvic_set_pending(void *opaque, int irq) } /* Make pending IRQ active. */ -void armv7m_nvic_acknowledge_irq(void *opaque) +bool armv7m_nvic_acknowledge_irq(void *opaque) { NVICState *s = (NVICState *)opaque; CPUARMState *env = &s->cpu->env; const int pending = s->vectpending; const int running = nvic_exec_prio(s); - int pendgroupprio; VecInfo *vec; + bool targets_secure; assert(pending > ARMV7M_EXCP_RESET && pending < s->num_irq); - vec = &s->vectors[pending]; + if (s->vectpending_is_s_banked) { + vec = &s->sec_vectors[pending]; + targets_secure = true; + } else { + vec = &s->vectors[pending]; + targets_secure = !exc_is_banked(s->vectpending) && + exc_targets_secure(s, s->vectpending); + } assert(vec->enabled); assert(vec->pending); - pendgroupprio = vec->prio; - if (pendgroupprio > 0) { - pendgroupprio &= nvic_gprio_mask(s); - } - assert(pendgroupprio < running); + assert(s->vectpending_prio < running); - trace_nvic_acknowledge_irq(pending, vec->prio); + trace_nvic_acknowledge_irq(pending, s->vectpending_prio, targets_secure); vec->active = 1; vec->pending = 0; @@ -347,9 +619,11 @@ void armv7m_nvic_acknowledge_irq(void *opaque) env->v7m.exception = s->vectpending; nvic_irq_update(s); + + return targets_secure; } -int armv7m_nvic_complete_irq(void *opaque, int irq) +int armv7m_nvic_complete_irq(void *opaque, int irq, bool secure) { NVICState *s = (NVICState *)opaque; VecInfo *vec; @@ -357,9 +631,13 @@ int armv7m_nvic_complete_irq(void *opaque, int irq) assert(irq > ARMV7M_EXCP_RESET && irq < s->num_irq); - vec = &s->vectors[irq]; + if (secure && exc_is_banked(irq)) { + vec = &s->sec_vectors[irq]; + } else { + vec = &s->vectors[irq]; + } - trace_nvic_complete_irq(irq); + trace_nvic_complete_irq(irq, secure); if (!vec->active) { /* Tell the caller this was an illegal exception return */ @@ -405,7 +683,7 @@ static void set_irq_level(void *opaque, int n, int level) if (level != vec->level) { vec->level = level; if (level) { - armv7m_nvic_set_pending(s, n); + armv7m_nvic_set_pending(s, n, false); } } } @@ -418,9 +696,28 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs) switch (offset) { case 4: /* Interrupt Control Type. */ return ((s->num_irq - NVIC_FIRST_IRQ) / 32) - 1; + case 0x380 ... 0x3bf: /* NVIC_ITNS<n> */ + { + int startvec = 32 * (offset - 0x380) + NVIC_FIRST_IRQ; + int i; + + if (!arm_feature(&cpu->env, ARM_FEATURE_V8)) { + goto bad_offset; + } + if (!attrs.secure) { + return 0; + } + val = 0; + for (i = 0; i < 32 && startvec + i < s->num_irq; i++) { + if (s->itns[startvec + i]) { + val |= (1 << i); + } + } + return val; + } case 0xd00: /* CPUID Base. */ return cpu->midr; - case 0xd04: /* Interrupt Control State. */ + case 0xd04: /* Interrupt Control State (ICSR) */ /* VECTACTIVE */ val = cpu->env.v7m.exception; /* VECTPENDING */ @@ -433,24 +730,50 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs) if (nvic_rettobase(s)) { val |= (1 << 11); } - /* PENDSTSET */ - if (s->vectors[ARMV7M_EXCP_SYSTICK].pending) { - val |= (1 << 26); - } - /* PENDSVSET */ - if (s->vectors[ARMV7M_EXCP_PENDSV].pending) { - val |= (1 << 28); + if (attrs.secure) { + /* PENDSTSET */ + if (s->sec_vectors[ARMV7M_EXCP_SYSTICK].pending) { + val |= (1 << 26); + } + /* PENDSVSET */ + if (s->sec_vectors[ARMV7M_EXCP_PENDSV].pending) { + val |= (1 << 28); + } + } else { + /* PENDSTSET */ + if (s->vectors[ARMV7M_EXCP_SYSTICK].pending) { + val |= (1 << 26); + } + /* PENDSVSET */ + if (s->vectors[ARMV7M_EXCP_PENDSV].pending) { + val |= (1 << 28); + } } /* NMIPENDSET */ - if (s->vectors[ARMV7M_EXCP_NMI].pending) { + if ((cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK) && + s->vectors[ARMV7M_EXCP_NMI].pending) { val |= (1 << 31); } - /* ISRPREEMPT not implemented */ + /* ISRPREEMPT: RES0 when halting debug not implemented */ + /* STTNS: RES0 for the Main Extension */ return val; case 0xd08: /* Vector Table Offset. */ return cpu->env.v7m.vecbase[attrs.secure]; - case 0xd0c: /* Application Interrupt/Reset Control. */ - return 0xfa050000 | (s->prigroup << 8); + case 0xd0c: /* Application Interrupt/Reset Control (AIRCR) */ + val = 0xfa050000 | (s->prigroup[attrs.secure] << 8); + if (attrs.secure) { + /* s->aircr stores PRIS, BFHFNMINS, SYSRESETREQS */ + val |= cpu->env.v7m.aircr; + } else { + if (arm_feature(&cpu->env, ARM_FEATURE_V8)) { + /* BFHFNMINS is R/O from NS; other bits are RAZ/WI. If + * security isn't supported then BFHFNMINS is RAO (and + * the bit in env.v7m.aircr is always set). + */ + val |= cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK; + } + } + return val; case 0xd10: /* System Control. */ /* TODO: Implement SLEEPONEXIT. */ return 0; @@ -461,50 +784,117 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs) val = cpu->env.v7m.ccr[attrs.secure]; val |= cpu->env.v7m.ccr[M_REG_NS] & R_V7M_CCR_BFHFNMIGN_MASK; return val; - case 0xd24: /* System Handler Status. */ + case 0xd24: /* System Handler Control and State (SHCSR) */ val = 0; - if (s->vectors[ARMV7M_EXCP_MEM].active) { - val |= (1 << 0); - } - if (s->vectors[ARMV7M_EXCP_BUS].active) { - val |= (1 << 1); - } - if (s->vectors[ARMV7M_EXCP_USAGE].active) { - val |= (1 << 3); + if (attrs.secure) { + if (s->sec_vectors[ARMV7M_EXCP_MEM].active) { + val |= (1 << 0); + } + if (s->sec_vectors[ARMV7M_EXCP_HARD].active) { + val |= (1 << 2); + } + if (s->sec_vectors[ARMV7M_EXCP_USAGE].active) { + val |= (1 << 3); + } + if (s->sec_vectors[ARMV7M_EXCP_SVC].active) { + val |= (1 << 7); + } + if (s->sec_vectors[ARMV7M_EXCP_PENDSV].active) { + val |= (1 << 10); + } + if (s->sec_vectors[ARMV7M_EXCP_SYSTICK].active) { + val |= (1 << 11); + } + if (s->sec_vectors[ARMV7M_EXCP_USAGE].pending) { + val |= (1 << 12); + } + if (s->sec_vectors[ARMV7M_EXCP_MEM].pending) { + val |= (1 << 13); + } + if (s->sec_vectors[ARMV7M_EXCP_SVC].pending) { + val |= (1 << 15); + } + if (s->sec_vectors[ARMV7M_EXCP_MEM].enabled) { + val |= (1 << 16); + } + if (s->sec_vectors[ARMV7M_EXCP_USAGE].enabled) { + val |= (1 << 18); + } + if (s->sec_vectors[ARMV7M_EXCP_HARD].pending) { + val |= (1 << 21); + } + /* SecureFault is not banked but is always RAZ/WI to NS */ + if (s->vectors[ARMV7M_EXCP_SECURE].active) { + val |= (1 << 4); + } + if (s->vectors[ARMV7M_EXCP_SECURE].enabled) { + val |= (1 << 19); + } + if (s->vectors[ARMV7M_EXCP_SECURE].pending) { + val |= (1 << 20); + } + } else { + if (s->vectors[ARMV7M_EXCP_MEM].active) { + val |= (1 << 0); + } + if (arm_feature(&cpu->env, ARM_FEATURE_V8)) { + /* HARDFAULTACT, HARDFAULTPENDED not present in v7M */ + if (s->vectors[ARMV7M_EXCP_HARD].active) { + val |= (1 << 2); + } + if (s->vectors[ARMV7M_EXCP_HARD].pending) { + val |= (1 << 21); + } + } + if (s->vectors[ARMV7M_EXCP_USAGE].active) { + val |= (1 << 3); + } + if (s->vectors[ARMV7M_EXCP_SVC].active) { + val |= (1 << 7); + } + if (s->vectors[ARMV7M_EXCP_PENDSV].active) { + val |= (1 << 10); + } + if (s->vectors[ARMV7M_EXCP_SYSTICK].active) { + val |= (1 << 11); + } + if (s->vectors[ARMV7M_EXCP_USAGE].pending) { + val |= (1 << 12); + } + if (s->vectors[ARMV7M_EXCP_MEM].pending) { + val |= (1 << 13); + } + if (s->vectors[ARMV7M_EXCP_SVC].pending) { + val |= (1 << 15); + } + if (s->vectors[ARMV7M_EXCP_MEM].enabled) { + val |= (1 << 16); + } + if (s->vectors[ARMV7M_EXCP_USAGE].enabled) { + val |= (1 << 18); + } } - if (s->vectors[ARMV7M_EXCP_SVC].active) { - val |= (1 << 7); + if (attrs.secure || (cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK)) { + if (s->vectors[ARMV7M_EXCP_BUS].active) { + val |= (1 << 1); + } + if (s->vectors[ARMV7M_EXCP_BUS].pending) { + val |= (1 << 14); + } + if (s->vectors[ARMV7M_EXCP_BUS].enabled) { + val |= (1 << 17); + } + if (arm_feature(&cpu->env, ARM_FEATURE_V8) && + s->vectors[ARMV7M_EXCP_NMI].active) { + /* NMIACT is not present in v7M */ + val |= (1 << 5); + } } + + /* TODO: this is RAZ/WI from NS if DEMCR.SDME is set */ if (s->vectors[ARMV7M_EXCP_DEBUG].active) { val |= (1 << 8); } - if (s->vectors[ARMV7M_EXCP_PENDSV].active) { - val |= (1 << 10); - } - if (s->vectors[ARMV7M_EXCP_SYSTICK].active) { - val |= (1 << 11); - } - if (s->vectors[ARMV7M_EXCP_USAGE].pending) { - val |= (1 << 12); - } - if (s->vectors[ARMV7M_EXCP_MEM].pending) { - val |= (1 << 13); - } - if (s->vectors[ARMV7M_EXCP_BUS].pending) { - val |= (1 << 14); - } - if (s->vectors[ARMV7M_EXCP_SVC].pending) { - val |= (1 << 15); - } - if (s->vectors[ARMV7M_EXCP_MEM].enabled) { - val |= (1 << 16); - } - if (s->vectors[ARMV7M_EXCP_BUS].enabled) { - val |= (1 << 17); - } - if (s->vectors[ARMV7M_EXCP_USAGE].enabled) { - val |= (1 << 18); - } return val; case 0xd28: /* Configurable Fault Status. */ /* The BFSR bits [15:8] are shared between security states @@ -640,40 +1030,87 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value, ARMCPU *cpu = s->cpu; switch (offset) { - case 0xd04: /* Interrupt Control State. */ - if (value & (1 << 31)) { - armv7m_nvic_set_pending(s, ARMV7M_EXCP_NMI); + case 0x380 ... 0x3bf: /* NVIC_ITNS<n> */ + { + int startvec = 32 * (offset - 0x380) + NVIC_FIRST_IRQ; + int i; + + if (!arm_feature(&cpu->env, ARM_FEATURE_V8)) { + goto bad_offset; + } + if (!attrs.secure) { + break; + } + for (i = 0; i < 32 && startvec + i < s->num_irq; i++) { + s->itns[startvec + i] = (value >> i) & 1; + } + nvic_irq_update(s); + break; + } + case 0xd04: /* Interrupt Control State (ICSR) */ + if (cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK) { + if (value & (1 << 31)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_NMI, false); + } else if (value & (1 << 30) && + arm_feature(&cpu->env, ARM_FEATURE_V8)) { + /* PENDNMICLR didn't exist in v7M */ + armv7m_nvic_clear_pending(s, ARMV7M_EXCP_NMI, false); + } } if (value & (1 << 28)) { - armv7m_nvic_set_pending(s, ARMV7M_EXCP_PENDSV); + armv7m_nvic_set_pending(s, ARMV7M_EXCP_PENDSV, attrs.secure); } else if (value & (1 << 27)) { - armv7m_nvic_clear_pending(s, ARMV7M_EXCP_PENDSV); + armv7m_nvic_clear_pending(s, ARMV7M_EXCP_PENDSV, attrs.secure); } if (value & (1 << 26)) { - armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK); + armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK, attrs.secure); } else if (value & (1 << 25)) { - armv7m_nvic_clear_pending(s, ARMV7M_EXCP_SYSTICK); + armv7m_nvic_clear_pending(s, ARMV7M_EXCP_SYSTICK, attrs.secure); } break; case 0xd08: /* Vector Table Offset. */ cpu->env.v7m.vecbase[attrs.secure] = value & 0xffffff80; break; - case 0xd0c: /* Application Interrupt/Reset Control. */ - if ((value >> 16) == 0x05fa) { - if (value & 4) { - qemu_irq_pulse(s->sysresetreq); + case 0xd0c: /* Application Interrupt/Reset Control (AIRCR) */ + if ((value >> R_V7M_AIRCR_VECTKEY_SHIFT) == 0x05fa) { + if (value & R_V7M_AIRCR_SYSRESETREQ_MASK) { + if (attrs.secure || + !(cpu->env.v7m.aircr & R_V7M_AIRCR_SYSRESETREQS_MASK)) { + qemu_irq_pulse(s->sysresetreq); + } } - if (value & 2) { + if (value & R_V7M_AIRCR_VECTCLRACTIVE_MASK) { qemu_log_mask(LOG_GUEST_ERROR, "Setting VECTCLRACTIVE when not in DEBUG mode " "is UNPREDICTABLE\n"); } - if (value & 1) { + if (value & R_V7M_AIRCR_VECTRESET_MASK) { + /* NB: this bit is RES0 in v8M */ qemu_log_mask(LOG_GUEST_ERROR, "Setting VECTRESET when not in DEBUG mode " "is UNPREDICTABLE\n"); } - s->prigroup = extract32(value, 8, 3); + s->prigroup[attrs.secure] = extract32(value, + R_V7M_AIRCR_PRIGROUP_SHIFT, + R_V7M_AIRCR_PRIGROUP_LENGTH); + if (attrs.secure) { + /* These bits are only writable by secure */ + cpu->env.v7m.aircr = value & + (R_V7M_AIRCR_SYSRESETREQS_MASK | + R_V7M_AIRCR_BFHFNMINS_MASK | + R_V7M_AIRCR_PRIS_MASK); + /* BFHFNMINS changes the priority of Secure HardFault, and + * allows a pending Non-secure HardFault to preempt (which + * we implement by marking it enabled). + */ + if (cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK) { + s->sec_vectors[ARMV7M_EXCP_HARD].prio = -3; + s->vectors[ARMV7M_EXCP_HARD].enabled = 1; + } else { + s->sec_vectors[ARMV7M_EXCP_HARD].prio = -1; + s->vectors[ARMV7M_EXCP_HARD].enabled = 0; + } + } nvic_irq_update(s); } break; @@ -705,21 +1142,71 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value, cpu->env.v7m.ccr[attrs.secure] = value; break; - case 0xd24: /* System Handler Control. */ - s->vectors[ARMV7M_EXCP_MEM].active = (value & (1 << 0)) != 0; - s->vectors[ARMV7M_EXCP_BUS].active = (value & (1 << 1)) != 0; - s->vectors[ARMV7M_EXCP_USAGE].active = (value & (1 << 3)) != 0; - s->vectors[ARMV7M_EXCP_SVC].active = (value & (1 << 7)) != 0; + case 0xd24: /* System Handler Control and State (SHCSR) */ + if (attrs.secure) { + s->sec_vectors[ARMV7M_EXCP_MEM].active = (value & (1 << 0)) != 0; + /* Secure HardFault active bit cannot be written */ + s->sec_vectors[ARMV7M_EXCP_USAGE].active = (value & (1 << 3)) != 0; + s->sec_vectors[ARMV7M_EXCP_SVC].active = (value & (1 << 7)) != 0; + s->sec_vectors[ARMV7M_EXCP_PENDSV].active = + (value & (1 << 10)) != 0; + s->sec_vectors[ARMV7M_EXCP_SYSTICK].active = + (value & (1 << 11)) != 0; + s->sec_vectors[ARMV7M_EXCP_USAGE].pending = + (value & (1 << 12)) != 0; + s->sec_vectors[ARMV7M_EXCP_MEM].pending = (value & (1 << 13)) != 0; + s->sec_vectors[ARMV7M_EXCP_SVC].pending = (value & (1 << 15)) != 0; + s->sec_vectors[ARMV7M_EXCP_MEM].enabled = (value & (1 << 16)) != 0; + s->sec_vectors[ARMV7M_EXCP_BUS].enabled = (value & (1 << 17)) != 0; + s->sec_vectors[ARMV7M_EXCP_USAGE].enabled = + (value & (1 << 18)) != 0; + /* SecureFault not banked, but RAZ/WI to NS */ + s->vectors[ARMV7M_EXCP_SECURE].active = (value & (1 << 4)) != 0; + s->vectors[ARMV7M_EXCP_SECURE].enabled = (value & (1 << 19)) != 0; + s->vectors[ARMV7M_EXCP_SECURE].pending = (value & (1 << 20)) != 0; + } else { + s->vectors[ARMV7M_EXCP_MEM].active = (value & (1 << 0)) != 0; + if (arm_feature(&cpu->env, ARM_FEATURE_V8)) { + /* HARDFAULTPENDED is not present in v7M */ + s->vectors[ARMV7M_EXCP_HARD].pending = (value & (1 << 21)) != 0; + } + s->vectors[ARMV7M_EXCP_USAGE].active = (value & (1 << 3)) != 0; + s->vectors[ARMV7M_EXCP_SVC].active = (value & (1 << 7)) != 0; + s->vectors[ARMV7M_EXCP_PENDSV].active = (value & (1 << 10)) != 0; + s->vectors[ARMV7M_EXCP_SYSTICK].active = (value & (1 << 11)) != 0; + s->vectors[ARMV7M_EXCP_USAGE].pending = (value & (1 << 12)) != 0; + s->vectors[ARMV7M_EXCP_MEM].pending = (value & (1 << 13)) != 0; + s->vectors[ARMV7M_EXCP_SVC].pending = (value & (1 << 15)) != 0; + s->vectors[ARMV7M_EXCP_MEM].enabled = (value & (1 << 16)) != 0; + s->vectors[ARMV7M_EXCP_USAGE].enabled = (value & (1 << 18)) != 0; + } + if (attrs.secure || (cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK)) { + s->vectors[ARMV7M_EXCP_BUS].active = (value & (1 << 1)) != 0; + s->vectors[ARMV7M_EXCP_BUS].pending = (value & (1 << 14)) != 0; + s->vectors[ARMV7M_EXCP_BUS].enabled = (value & (1 << 17)) != 0; + } + /* NMIACT can only be written if the write is of a zero, with + * BFHFNMINS 1, and by the CPU in secure state via the NS alias. + */ + if (!attrs.secure && cpu->env.v7m.secure && + (cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK) && + (value & (1 << 5)) == 0) { + s->vectors[ARMV7M_EXCP_NMI].active = 0; + } + /* HARDFAULTACT can only be written if the write is of a zero + * to the non-secure HardFault state by the CPU in secure state. + * The only case where we can be targeting the non-secure HF state + * when in secure state is if this is a write via the NS alias + * and BFHFNMINS is 1. + */ + if (!attrs.secure && cpu->env.v7m.secure && + (cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK) && + (value & (1 << 2)) == 0) { + s->vectors[ARMV7M_EXCP_HARD].active = 0; + } + + /* TODO: this is RAZ/WI from NS if DEMCR.SDME is set */ s->vectors[ARMV7M_EXCP_DEBUG].active = (value & (1 << 8)) != 0; - s->vectors[ARMV7M_EXCP_PENDSV].active = (value & (1 << 10)) != 0; - s->vectors[ARMV7M_EXCP_SYSTICK].active = (value & (1 << 11)) != 0; - s->vectors[ARMV7M_EXCP_USAGE].pending = (value & (1 << 12)) != 0; - s->vectors[ARMV7M_EXCP_MEM].pending = (value & (1 << 13)) != 0; - s->vectors[ARMV7M_EXCP_BUS].pending = (value & (1 << 14)) != 0; - s->vectors[ARMV7M_EXCP_SVC].pending = (value & (1 << 15)) != 0; - s->vectors[ARMV7M_EXCP_MEM].enabled = (value & (1 << 16)) != 0; - s->vectors[ARMV7M_EXCP_BUS].enabled = (value & (1 << 17)) != 0; - s->vectors[ARMV7M_EXCP_USAGE].enabled = (value & (1 << 18)) != 0; nvic_irq_update(s); break; case 0xd28: /* Configurable Fault Status. */ @@ -885,7 +1372,7 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value, { int excnum = (value & 0x1ff) + NVIC_FIRST_IRQ; if (excnum < s->num_irq) { - armv7m_nvic_set_pending(s, excnum); + armv7m_nvic_set_pending(s, excnum, false); } break; } @@ -911,6 +1398,47 @@ static bool nvic_user_access_ok(NVICState *s, hwaddr offset, MemTxAttrs attrs) } } +static int shpr_bank(NVICState *s, int exc, MemTxAttrs attrs) +{ + /* Behaviour for the SHPR register field for this exception: + * return M_REG_NS to use the nonsecure vector (including for + * non-banked exceptions), M_REG_S for the secure version of + * a banked exception, and -1 if this field should RAZ/WI. + */ + switch (exc) { + case ARMV7M_EXCP_MEM: + case ARMV7M_EXCP_USAGE: + case ARMV7M_EXCP_SVC: + case ARMV7M_EXCP_PENDSV: + case ARMV7M_EXCP_SYSTICK: + /* Banked exceptions */ + return attrs.secure; + case ARMV7M_EXCP_BUS: + /* Not banked, RAZ/WI from nonsecure if BFHFNMINS is zero */ + if (!attrs.secure && + !(s->cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK)) { + return -1; + } + return M_REG_NS; + case ARMV7M_EXCP_SECURE: + /* Not banked, RAZ/WI from nonsecure */ + if (!attrs.secure) { + return -1; + } + return M_REG_NS; + case ARMV7M_EXCP_DEBUG: + /* Not banked. TODO should RAZ/WI if DEMCR.SDME is set */ + return M_REG_NS; + case 8 ... 10: + case 13: + /* RES0 */ + return -1; + default: + /* Not reachable due to decode of SHPR register addresses */ + g_assert_not_reached(); + } +} + static MemTxResult nvic_sysreg_read(void *opaque, hwaddr addr, uint64_t *data, unsigned size, MemTxAttrs attrs) @@ -935,7 +1463,8 @@ static MemTxResult nvic_sysreg_read(void *opaque, hwaddr addr, startvec = offset - 0x180 + NVIC_FIRST_IRQ; /* vector # */ for (i = 0, end = size * 8; i < end && startvec + i < s->num_irq; i++) { - if (s->vectors[startvec + i].enabled) { + if (s->vectors[startvec + i].enabled && + (attrs.secure || s->itns[startvec + i])) { val |= (1 << i); } } @@ -947,7 +1476,8 @@ static MemTxResult nvic_sysreg_read(void *opaque, hwaddr addr, val = 0; startvec = offset - 0x280 + NVIC_FIRST_IRQ; /* vector # */ for (i = 0, end = size * 8; i < end && startvec + i < s->num_irq; i++) { - if (s->vectors[startvec + i].pending) { + if (s->vectors[startvec + i].pending && + (attrs.secure || s->itns[startvec + i])) { val |= (1 << i); } } @@ -957,7 +1487,8 @@ static MemTxResult nvic_sysreg_read(void *opaque, hwaddr addr, startvec = offset - 0x300 + NVIC_FIRST_IRQ; /* vector # */ for (i = 0, end = size * 8; i < end && startvec + i < s->num_irq; i++) { - if (s->vectors[startvec + i].active) { + if (s->vectors[startvec + i].active && + (attrs.secure || s->itns[startvec + i])) { val |= (1 << i); } } @@ -967,13 +1498,21 @@ static MemTxResult nvic_sysreg_read(void *opaque, hwaddr addr, startvec = offset - 0x400 + NVIC_FIRST_IRQ; /* vector # */ for (i = 0; i < size && startvec + i < s->num_irq; i++) { - val |= s->vectors[startvec + i].prio << (8 * i); + if (attrs.secure || s->itns[startvec + i]) { + val |= s->vectors[startvec + i].prio << (8 * i); + } } break; - case 0xd18 ... 0xd23: /* System Handler Priority. */ + case 0xd18 ... 0xd23: /* System Handler Priority (SHPR1, SHPR2, SHPR3) */ val = 0; for (i = 0; i < size; i++) { - val |= s->vectors[(offset - 0xd14) + i].prio << (i * 8); + unsigned hdlidx = (offset - 0xd14) + i; + int sbank = shpr_bank(s, hdlidx, attrs); + + if (sbank < 0) { + continue; + } + val = deposit32(val, i * 8, 8, get_prio(s, hdlidx, sbank)); } break; case 0xfe0 ... 0xfff: /* ID. */ @@ -1024,7 +1563,8 @@ static MemTxResult nvic_sysreg_write(void *opaque, hwaddr addr, startvec = 8 * (offset - 0x180) + NVIC_FIRST_IRQ; for (i = 0, end = size * 8; i < end && startvec + i < s->num_irq; i++) { - if (value & (1 << i)) { + if (value & (1 << i) && + (attrs.secure || s->itns[startvec + i])) { s->vectors[startvec + i].enabled = setval; } } @@ -1041,7 +1581,8 @@ static MemTxResult nvic_sysreg_write(void *opaque, hwaddr addr, startvec = 8 * (offset - 0x280) + NVIC_FIRST_IRQ; /* vector # */ for (i = 0, end = size * 8; i < end && startvec + i < s->num_irq; i++) { - if (value & (1 << i)) { + if (value & (1 << i) && + (attrs.secure || s->itns[startvec + i])) { s->vectors[startvec + i].pending = setval; } } @@ -1053,14 +1594,22 @@ static MemTxResult nvic_sysreg_write(void *opaque, hwaddr addr, startvec = 8 * (offset - 0x400) + NVIC_FIRST_IRQ; /* vector # */ for (i = 0; i < size && startvec + i < s->num_irq; i++) { - set_prio(s, startvec + i, (value >> (i * 8)) & 0xff); + if (attrs.secure || s->itns[startvec + i]) { + set_prio(s, startvec + i, false, (value >> (i * 8)) & 0xff); + } } nvic_irq_update(s); return MEMTX_OK; - case 0xd18 ... 0xd23: /* System Handler Priority. */ + case 0xd18 ... 0xd23: /* System Handler Priority (SHPR1, SHPR2, SHPR3) */ for (i = 0; i < size; i++) { unsigned hdlidx = (offset - 0xd14) + i; - set_prio(s, hdlidx, (value >> (i * 8)) & 0xff); + int newprio = extract32(value, i * 8, 8); + int sbank = shpr_bank(s, hdlidx, attrs); + + if (sbank < 0) { + continue; + } + set_prio(s, hdlidx, sbank, newprio); } nvic_irq_update(s); return MEMTX_OK; @@ -1126,9 +1675,12 @@ static int nvic_post_load(void *opaque, int version_id) { NVICState *s = opaque; unsigned i; + int resetprio; /* Check for out of range priority settings */ - if (s->vectors[ARMV7M_EXCP_RESET].prio != -3 || + resetprio = arm_feature(&s->cpu->env, ARM_FEATURE_V8) ? -4 : -3; + + if (s->vectors[ARMV7M_EXCP_RESET].prio != resetprio || s->vectors[ARMV7M_EXCP_NMI].prio != -2 || s->vectors[ARMV7M_EXCP_HARD].prio != -1) { return 1; @@ -1158,6 +1710,50 @@ static const VMStateDescription vmstate_VecInfo = { } }; +static bool nvic_security_needed(void *opaque) +{ + NVICState *s = opaque; + + return arm_feature(&s->cpu->env, ARM_FEATURE_M_SECURITY); +} + +static int nvic_security_post_load(void *opaque, int version_id) +{ + NVICState *s = opaque; + int i; + + /* Check for out of range priority settings */ + if (s->sec_vectors[ARMV7M_EXCP_HARD].prio != -1 + && s->sec_vectors[ARMV7M_EXCP_HARD].prio != -3) { + /* We can't cross-check against AIRCR.BFHFNMINS as we don't know + * if the CPU state has been migrated yet; a mismatch won't + * cause the emulation to blow up, though. + */ + return 1; + } + for (i = ARMV7M_EXCP_MEM; i < ARRAY_SIZE(s->sec_vectors); i++) { + if (s->sec_vectors[i].prio & ~0xff) { + return 1; + } + } + return 0; +} + +static const VMStateDescription vmstate_nvic_security = { + .name = "nvic/m-security", + .version_id = 1, + .minimum_version_id = 1, + .needed = nvic_security_needed, + .post_load = &nvic_security_post_load, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(sec_vectors, NVICState, NVIC_INTERNAL_VECTORS, 1, + vmstate_VecInfo, VecInfo), + VMSTATE_UINT32(prigroup[M_REG_S], NVICState), + VMSTATE_BOOL_ARRAY(itns, NVICState, NVIC_MAX_VECTORS), + VMSTATE_END_OF_LIST() + } +}; + static const VMStateDescription vmstate_nvic = { .name = "armv7m_nvic", .version_id = 4, @@ -1166,8 +1762,12 @@ static const VMStateDescription vmstate_nvic = { .fields = (VMStateField[]) { VMSTATE_STRUCT_ARRAY(vectors, NVICState, NVIC_MAX_VECTORS, 1, vmstate_VecInfo, VecInfo), - VMSTATE_UINT32(prigroup, NVICState), + VMSTATE_UINT32(prigroup[M_REG_NS], NVICState), VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription*[]) { + &vmstate_nvic_security, + NULL } }; @@ -1179,10 +1779,10 @@ static Property props_nvic[] = { static void armv7m_nvic_reset(DeviceState *dev) { + int resetprio; NVICState *s = NVIC(dev); s->vectors[ARMV7M_EXCP_NMI].enabled = 1; - s->vectors[ARMV7M_EXCP_HARD].enabled = 1; /* MEM, BUS, and USAGE are enabled through * the System Handler Control register */ @@ -1191,10 +1791,25 @@ static void armv7m_nvic_reset(DeviceState *dev) s->vectors[ARMV7M_EXCP_PENDSV].enabled = 1; s->vectors[ARMV7M_EXCP_SYSTICK].enabled = 1; - s->vectors[ARMV7M_EXCP_RESET].prio = -3; + resetprio = arm_feature(&s->cpu->env, ARM_FEATURE_V8) ? -4 : -3; + s->vectors[ARMV7M_EXCP_RESET].prio = resetprio; s->vectors[ARMV7M_EXCP_NMI].prio = -2; s->vectors[ARMV7M_EXCP_HARD].prio = -1; + if (arm_feature(&s->cpu->env, ARM_FEATURE_M_SECURITY)) { + s->sec_vectors[ARMV7M_EXCP_HARD].enabled = 1; + s->sec_vectors[ARMV7M_EXCP_SVC].enabled = 1; + s->sec_vectors[ARMV7M_EXCP_PENDSV].enabled = 1; + s->sec_vectors[ARMV7M_EXCP_SYSTICK].enabled = 1; + + /* AIRCR.BFHFNMINS resets to 0 so Secure HF is priority -1 (R_CMTC) */ + s->sec_vectors[ARMV7M_EXCP_HARD].prio = -1; + /* If AIRCR.BFHFNMINS is 0 then NS HF is (effectively) disabled */ + s->vectors[ARMV7M_EXCP_HARD].enabled = 0; + } else { + s->vectors[ARMV7M_EXCP_HARD].enabled = 1; + } + /* Strictly speaking the reset handler should be enabled. * However, we don't simulate soft resets through the NVIC, * and the reset vector should never be pended. @@ -1203,6 +1818,22 @@ static void armv7m_nvic_reset(DeviceState *dev) s->exception_prio = NVIC_NOEXC_PRIO; s->vectpending = 0; + s->vectpending_is_s_banked = false; + s->vectpending_prio = NVIC_NOEXC_PRIO; + + if (arm_feature(&s->cpu->env, ARM_FEATURE_M_SECURITY)) { + memset(s->itns, 0, sizeof(s->itns)); + } else { + /* This state is constant and not guest accessible in a non-security + * NVIC; we set the bits to true to avoid having to do a feature + * bit check in the NVIC enable/pend/etc register accessors. + */ + int i; + + for (i = NVIC_FIRST_IRQ; i < ARRAY_SIZE(s->itns); i++) { + s->itns[i] = true; + } + } } static void nvic_systick_trigger(void *opaque, int n, int level) @@ -1213,8 +1844,10 @@ static void nvic_systick_trigger(void *opaque, int n, int level) /* SysTick just asked us to pend its exception. * (This is different from an external interrupt line's * behaviour.) + * TODO: when we implement the banked systicks we must make + * this pend the correct banked exception. */ - armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK); + armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK, false); } } diff --git a/hw/intc/trace-events b/hw/intc/trace-events index 4762329482..b86f242b0f 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -167,16 +167,17 @@ gicv3_redist_set_irq(uint32_t cpu, int irq, int level) "GICv3 redistributor 0x%x gicv3_redist_send_sgi(uint32_t cpu, int irq) "GICv3 redistributor 0x%x pending SGI %d" # hw/intc/armv7m_nvic.c -nvic_recompute_state(int vectpending, int exception_prio) "NVIC state recomputed: vectpending %d exception_prio %d" -nvic_set_prio(int irq, uint8_t prio) "NVIC set irq %d priority %d" +nvic_recompute_state(int vectpending, int vectpending_prio, int exception_prio) "NVIC state recomputed: vectpending %d vectpending_prio %d exception_prio %d" +nvic_recompute_state_secure(int vectpending, bool vectpending_is_s_banked, int vectpending_prio, int exception_prio) "NVIC state recomputed: vectpending %d is_s_banked %d vectpending_prio %d exception_prio %d" +nvic_set_prio(int irq, bool secure, uint8_t prio) "NVIC set irq %d secure-bank %d priority %d" nvic_irq_update(int vectpending, int pendprio, int exception_prio, int level) "NVIC vectpending %d pending prio %d exception_prio %d: setting irq line to %d" nvic_escalate_prio(int irq, int irqprio, int runprio) "NVIC escalating irq %d to HardFault: insufficient priority %d >= %d" nvic_escalate_disabled(int irq) "NVIC escalating irq %d to HardFault: disabled" -nvic_set_pending(int irq, int en, int prio) "NVIC set pending irq %d (enabled: %d priority %d)" -nvic_clear_pending(int irq, int en, int prio) "NVIC clear pending irq %d (enabled: %d priority %d)" +nvic_set_pending(int irq, bool secure, int en, int prio) "NVIC set pending irq %d secure-bank %d (enabled: %d priority %d)" +nvic_clear_pending(int irq, bool secure, int en, int prio) "NVIC clear pending irq %d secure-bank %d (enabled: %d priority %d)" nvic_set_pending_level(int irq) "NVIC set pending: irq %d higher prio than vectpending: setting irq line to 1" -nvic_acknowledge_irq(int irq, int prio) "NVIC acknowledge IRQ: %d now active (prio %d)" -nvic_complete_irq(int irq) "NVIC complete IRQ %d" +nvic_acknowledge_irq(int irq, int prio, bool targets_secure) "NVIC acknowledge IRQ: %d now active (prio %d targets_secure %d)" +nvic_complete_irq(int irq, bool secure) "NVIC complete IRQ %d (secure %d)" nvic_set_irq_level(int irq, int level) "NVIC external irq %d level set to %d" nvic_sysreg_read(uint64_t addr, uint32_t value, unsigned size) "NVIC sysreg read addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u" nvic_sysreg_write(uint64_t addr, uint32_t value, unsigned size) "NVIC sysreg write addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u" diff --git a/hw/mips/Makefile.objs b/hw/mips/Makefile.objs index 48cd2ef50e..17a311aaba 100644 --- a/hw/mips/Makefile.objs +++ b/hw/mips/Makefile.objs @@ -1,5 +1,5 @@ obj-y += mips_r4k.o mips_malta.o mips_mipssim.o -obj-y += addr.o cputimer.o mips_int.o +obj-y += addr.o mips_int.o obj-$(CONFIG_JAZZ) += mips_jazz.o obj-$(CONFIG_FULONG) += mips_fulong2e.o obj-y += gt64xxx_pci.o diff --git a/hw/mips/cps.c b/hw/mips/cps.c index 79d4c5e30a..fe5c630af6 100644 --- a/hw/mips/cps.c +++ b/hw/mips/cps.c @@ -71,7 +71,7 @@ static void mips_cps_realize(DeviceState *dev, Error **errp) bool itu_present = false; for (i = 0; i < s->num_vp; i++) { - cpu = cpu_mips_init(s->cpu_model); + cpu = MIPS_CPU(cpu_generic_init(TYPE_MIPS_CPU, s->cpu_model)); /* Init internal devices */ cpu_mips_irq_init_cpu(cpu); diff --git a/hw/mips/cputimer.c b/hw/mips/cputimer.c deleted file mode 100644 index 8a166b3ea7..0000000000 --- a/hw/mips/cputimer.c +++ /dev/null @@ -1,165 +0,0 @@ -/* - * QEMU MIPS timer support - * - * 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/hw.h" -#include "hw/mips/cpudevs.h" -#include "qemu/timer.h" -#include "sysemu/kvm.h" - -#define TIMER_PERIOD 10 /* 10 ns period for 100 Mhz frequency */ - -/* XXX: do not use a global */ -uint32_t cpu_mips_get_random (CPUMIPSState *env) -{ - static uint32_t seed = 1; - static uint32_t prev_idx = 0; - uint32_t idx; - uint32_t nb_rand_tlb = env->tlb->nb_tlb - env->CP0_Wired; - - if (nb_rand_tlb == 1) { - return env->tlb->nb_tlb - 1; - } - - /* Don't return same value twice, so get another value */ - do { - /* Use a simple algorithm of Linear Congruential Generator - * from ISO/IEC 9899 standard. */ - seed = 1103515245 * seed + 12345; - idx = (seed >> 16) % nb_rand_tlb + env->CP0_Wired; - } while (idx == prev_idx); - prev_idx = idx; - return idx; -} - -/* MIPS R4K timer */ -static void cpu_mips_timer_update(CPUMIPSState *env) -{ - uint64_t now, next; - uint32_t wait; - - now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - wait = env->CP0_Compare - env->CP0_Count - (uint32_t)(now / TIMER_PERIOD); - next = now + (uint64_t)wait * TIMER_PERIOD; - timer_mod(env->timer, next); -} - -/* Expire the timer. */ -static void cpu_mips_timer_expire(CPUMIPSState *env) -{ - cpu_mips_timer_update(env); - if (env->insn_flags & ISA_MIPS32R2) { - env->CP0_Cause |= 1 << CP0Ca_TI; - } - qemu_irq_raise(env->irq[(env->CP0_IntCtl >> CP0IntCtl_IPTI) & 0x7]); -} - -uint32_t cpu_mips_get_count (CPUMIPSState *env) -{ - if (env->CP0_Cause & (1 << CP0Ca_DC)) { - return env->CP0_Count; - } else { - uint64_t now; - - now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - if (timer_pending(env->timer) - && timer_expired(env->timer, now)) { - /* The timer has already expired. */ - cpu_mips_timer_expire(env); - } - - return env->CP0_Count + (uint32_t)(now / TIMER_PERIOD); - } -} - -void cpu_mips_store_count (CPUMIPSState *env, uint32_t count) -{ - /* - * This gets called from cpu_state_reset(), potentially before timer init. - * So env->timer may be NULL, which is also the case with KVM enabled so - * treat timer as disabled in that case. - */ - if (env->CP0_Cause & (1 << CP0Ca_DC) || !env->timer) - env->CP0_Count = count; - else { - /* Store new count register */ - env->CP0_Count = count - - (uint32_t)(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / TIMER_PERIOD); - /* Update timer timer */ - cpu_mips_timer_update(env); - } -} - -void cpu_mips_store_compare (CPUMIPSState *env, uint32_t value) -{ - env->CP0_Compare = value; - if (!(env->CP0_Cause & (1 << CP0Ca_DC))) - cpu_mips_timer_update(env); - if (env->insn_flags & ISA_MIPS32R2) - env->CP0_Cause &= ~(1 << CP0Ca_TI); - qemu_irq_lower(env->irq[(env->CP0_IntCtl >> CP0IntCtl_IPTI) & 0x7]); -} - -void cpu_mips_start_count(CPUMIPSState *env) -{ - cpu_mips_store_count(env, env->CP0_Count); -} - -void cpu_mips_stop_count(CPUMIPSState *env) -{ - /* Store the current value */ - env->CP0_Count += (uint32_t)(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / - TIMER_PERIOD); -} - -static void mips_timer_cb (void *opaque) -{ - CPUMIPSState *env; - - env = opaque; -#if 0 - qemu_log("%s\n", __func__); -#endif - - if (env->CP0_Cause & (1 << CP0Ca_DC)) - return; - - /* ??? This callback should occur when the counter is exactly equal to - the comparator value. Offset the count by one to avoid immediately - retriggering the callback before any virtual time has passed. */ - env->CP0_Count++; - cpu_mips_timer_expire(env); - env->CP0_Count--; -} - -void cpu_mips_clock_init (MIPSCPU *cpu) -{ - CPUMIPSState *env = &cpu->env; - - /* - * If we're in KVM mode, don't create the periodic timer, that is handled in - * kernel. - */ - if (!kvm_enabled()) { - env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &mips_timer_cb, env); - } -} diff --git a/hw/mips/mips_fulong2e.c b/hw/mips/mips_fulong2e.c index 439a3d7a66..75318680e1 100644 --- a/hw/mips/mips_fulong2e.c +++ b/hw/mips/mips_fulong2e.c @@ -280,7 +280,7 @@ static void mips_fulong2e_init(MachineState *machine) if (cpu_model == NULL) { cpu_model = "Loongson-2E"; } - cpu = cpu_mips_init(cpu_model); + cpu = MIPS_CPU(cpu_generic_init(TYPE_MIPS_CPU, cpu_model)); env = &cpu->env; qemu_register_reset(main_cpu_reset, cpu); diff --git a/hw/mips/mips_jazz.c b/hw/mips/mips_jazz.c index ae10670efd..7e6626dc88 100644 --- a/hw/mips/mips_jazz.c +++ b/hw/mips/mips_jazz.c @@ -151,7 +151,7 @@ static void mips_jazz_init(MachineState *machine, if (cpu_model == NULL) { cpu_model = "R4000"; } - cpu = cpu_mips_init(cpu_model); + cpu = MIPS_CPU(cpu_generic_init(TYPE_MIPS_CPU, cpu_model)); env = &cpu->env; qemu_register_reset(main_cpu_reset, cpu); diff --git a/hw/mips/mips_malta.c b/hw/mips/mips_malta.c index e87cd3230b..2adb9bcf89 100644 --- a/hw/mips/mips_malta.c +++ b/hw/mips/mips_malta.c @@ -931,7 +931,7 @@ static void create_cpu_without_cps(const char *cpu_model, int i; for (i = 0; i < smp_cpus; i++) { - cpu = cpu_mips_init(cpu_model); + cpu = MIPS_CPU(cpu_generic_init(TYPE_MIPS_CPU, cpu_model)); /* Init internal devices */ cpu_mips_irq_init_cpu(cpu); diff --git a/hw/mips/mips_mipssim.c b/hw/mips/mips_mipssim.c index 49cd38d680..a092072e2a 100644 --- a/hw/mips/mips_mipssim.c +++ b/hw/mips/mips_mipssim.c @@ -163,7 +163,7 @@ mips_mipssim_init(MachineState *machine) cpu_model = "24Kf"; #endif } - cpu = cpu_mips_init(cpu_model); + cpu = MIPS_CPU(cpu_generic_init(TYPE_MIPS_CPU, cpu_model)); env = &cpu->env; reset_info = g_malloc0(sizeof(ResetData)); diff --git a/hw/mips/mips_r4k.c b/hw/mips/mips_r4k.c index 7efee94431..1272d4ef9d 100644 --- a/hw/mips/mips_r4k.c +++ b/hw/mips/mips_r4k.c @@ -193,7 +193,7 @@ void mips_r4k_init(MachineState *machine) cpu_model = "24Kf"; #endif } - cpu = cpu_mips_init(cpu_model); + cpu = MIPS_CPU(cpu_generic_init(TYPE_MIPS_CPU, cpu_model)); env = &cpu->env; reset_info = g_malloc0(sizeof(ResetData)); diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index 29fb922cef..e8f0a02f35 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -59,3 +59,4 @@ obj-$(CONFIG_HYPERV_TESTDEV) += hyperv_testdev.o obj-$(CONFIG_AUX) += auxbus.o obj-$(CONFIG_ASPEED_SOC) += aspeed_scu.o aspeed_sdmc.o obj-y += mmio_interface.o +obj-$(CONFIG_MSF2) += msf2-sysreg.o diff --git a/hw/misc/msf2-sysreg.c b/hw/misc/msf2-sysreg.c new file mode 100644 index 0000000000..6eb501104b --- /dev/null +++ b/hw/misc/msf2-sysreg.c @@ -0,0 +1,160 @@ +/* + * System Register block model of Microsemi SmartFusion2. + * + * Copyright (c) 2017 Subbaraya Sundeep <sundeep.lkml@gmail.com> + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/log.h" +#include "hw/misc/msf2-sysreg.h" +#include "qemu/error-report.h" +#include "trace.h" + +static inline int msf2_divbits(uint32_t div) +{ + int r = ctz32(div); + + return (div < 8) ? r : r + 1; +} + +static void msf2_sysreg_reset(DeviceState *d) +{ + MSF2SysregState *s = MSF2_SYSREG(d); + + s->regs[MSSDDR_PLL_STATUS_LOW_CR] = 0x021A2358; + s->regs[MSSDDR_PLL_STATUS] = 0x3; + s->regs[MSSDDR_FACC1_CR] = msf2_divbits(s->apb0div) << 5 | + msf2_divbits(s->apb1div) << 2; +} + +static uint64_t msf2_sysreg_read(void *opaque, hwaddr offset, + unsigned size) +{ + MSF2SysregState *s = opaque; + uint32_t ret = 0; + + offset >>= 2; + if (offset < ARRAY_SIZE(s->regs)) { + ret = s->regs[offset]; + trace_msf2_sysreg_read(offset << 2, ret); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%08" HWADDR_PRIx "\n", __func__, + offset << 2); + } + + return ret; +} + +static void msf2_sysreg_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + MSF2SysregState *s = opaque; + uint32_t newval = val; + + offset >>= 2; + + switch (offset) { + case MSSDDR_PLL_STATUS: + trace_msf2_sysreg_write_pll_status(); + break; + + case ESRAM_CR: + case DDR_CR: + case ENVM_REMAP_BASE_CR: + if (newval != s->regs[offset]) { + qemu_log_mask(LOG_UNIMP, + TYPE_MSF2_SYSREG": remapping not supported\n"); + } + break; + + default: + if (offset < ARRAY_SIZE(s->regs)) { + trace_msf2_sysreg_write(offset << 2, newval, s->regs[offset]); + s->regs[offset] = newval; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%08" HWADDR_PRIx "\n", __func__, + offset << 2); + } + break; + } +} + +static const MemoryRegionOps sysreg_ops = { + .read = msf2_sysreg_read, + .write = msf2_sysreg_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void msf2_sysreg_init(Object *obj) +{ + MSF2SysregState *s = MSF2_SYSREG(obj); + + memory_region_init_io(&s->iomem, obj, &sysreg_ops, s, TYPE_MSF2_SYSREG, + MSF2_SYSREG_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); +} + +static const VMStateDescription vmstate_msf2_sysreg = { + .name = TYPE_MSF2_SYSREG, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, MSF2SysregState, MSF2_SYSREG_MMIO_SIZE / 4), + VMSTATE_END_OF_LIST() + } +}; + +static Property msf2_sysreg_properties[] = { + /* default divisors in Libero GUI */ + DEFINE_PROP_UINT8("apb0divisor", MSF2SysregState, apb0div, 2), + DEFINE_PROP_UINT8("apb1divisor", MSF2SysregState, apb1div, 2), + DEFINE_PROP_END_OF_LIST(), +}; + +static void msf2_sysreg_realize(DeviceState *dev, Error **errp) +{ + MSF2SysregState *s = MSF2_SYSREG(dev); + + if ((s->apb0div > 32 || !is_power_of_2(s->apb0div)) + || (s->apb1div > 32 || !is_power_of_2(s->apb1div))) { + error_setg(errp, "Invalid apb divisor value"); + error_append_hint(errp, "apb divisor must be a power of 2" + " and maximum value is 32\n"); + } +} + +static void msf2_sysreg_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_msf2_sysreg; + dc->reset = msf2_sysreg_reset; + dc->props = msf2_sysreg_properties; + dc->realize = msf2_sysreg_realize; +} + +static const TypeInfo msf2_sysreg_info = { + .name = TYPE_MSF2_SYSREG, + .parent = TYPE_SYS_BUS_DEVICE, + .class_init = msf2_sysreg_class_init, + .instance_size = sizeof(MSF2SysregState), + .instance_init = msf2_sysreg_init, +}; + +static void msf2_sysreg_register_types(void) +{ + type_register_static(&msf2_sysreg_info); +} + +type_init(msf2_sysreg_register_types) diff --git a/hw/misc/trace-events b/hw/misc/trace-events index 3313585b12..616579a403 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -61,3 +61,8 @@ mps2_scc_reset(void) "MPS2 SCC: reset" mps2_scc_leds(char led7, char led6, char led5, char led4, char led3, char led2, char led1, char led0) "MPS2 SCC LEDs: %c%c%c%c%c%c%c%c" mps2_scc_cfg_write(unsigned function, unsigned device, uint32_t value) "MPS2 SCC config write: function %d device %d data 0x%" PRIx32 mps2_scc_cfg_read(unsigned function, unsigned device, uint32_t value) "MPS2 SCC config read: function %d device %d data 0x%" PRIx32 + +# hw/misc/msf2-sysreg.c +msf2_sysreg_write(uint64_t offset, uint32_t val, uint32_t prev) "msf2-sysreg write: addr 0x%08" HWADDR_PRIx " data 0x%" PRIx32 " prev 0x%" PRIx32 +msf2_sysreg_read(uint64_t offset, uint32_t val) "msf2-sysreg read: addr 0x%08" HWADDR_PRIx " data 0x%08" PRIx32 +msf2_sysreg_write_pll_status(void) "Invalid write to read only PLL status register" diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs index 7e87d0176b..4171af0b5d 100644 --- a/hw/net/Makefile.objs +++ b/hw/net/Makefile.objs @@ -26,6 +26,7 @@ common-obj-$(CONFIG_IMX_FEC) += imx_fec.o common-obj-$(CONFIG_CADENCE) += cadence_gem.o common-obj-$(CONFIG_STELLARIS_ENET) += stellaris_enet.o common-obj-$(CONFIG_LANCE) += lance.o +common-obj-$(CONFIG_SUNHME) += sunhme.o common-obj-$(CONFIG_FTGMAC100) += ftgmac100.o common-obj-$(CONFIG_SUNGEM) += sungem.o diff --git a/hw/net/sunhme.c b/hw/net/sunhme.c new file mode 100644 index 0000000000..60277adcf1 --- /dev/null +++ b/hw/net/sunhme.c @@ -0,0 +1,978 @@ +/* + * QEMU Sun Happy Meal Ethernet emulation + * + * Copyright (c) 2017 Mark Cave-Ayland + * + * 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/hw.h" +#include "hw/pci/pci.h" +#include "hw/net/mii.h" +#include "net/net.h" +#include "net/checksum.h" +#include "net/eth.h" +#include "sysemu/sysemu.h" +#include "trace.h" + +#define HME_REG_SIZE 0x8000 + +#define HME_SEB_REG_SIZE 0x2000 + +#define HME_SEBI_RESET 0x0 +#define HME_SEB_RESET_ETX 0x1 +#define HME_SEB_RESET_ERX 0x2 + +#define HME_SEBI_STAT 0x100 +#define HME_SEBI_STAT_LINUXBUG 0x108 +#define HME_SEB_STAT_RXTOHOST 0x10000 +#define HME_SEB_STAT_MIFIRQ 0x800000 +#define HME_SEB_STAT_HOSTTOTX 0x1000000 +#define HME_SEB_STAT_TXALL 0x2000000 + +#define HME_SEBI_IMASK 0x104 +#define HME_SEBI_IMASK_LINUXBUG 0x10c + +#define HME_ETX_REG_SIZE 0x2000 + +#define HME_ETXI_PENDING 0x0 + +#define HME_ETXI_RING 0x8 +#define HME_ETXI_RING_ADDR 0xffffff00 +#define HME_ETXI_RING_OFFSET 0xff + +#define HME_ETXI_RSIZE 0x2c + +#define HME_ERX_REG_SIZE 0x2000 + +#define HME_ERXI_CFG 0x0 +#define HME_ERX_CFG_RINGSIZE 0x600 +#define HME_ERX_CFG_RINGSIZE_SHIFT 9 +#define HME_ERX_CFG_BYTEOFFSET 0x38 +#define HME_ERX_CFG_BYTEOFFSET_SHIFT 3 +#define HME_ERX_CFG_CSUMSTART 0x7f0000 +#define HME_ERX_CFG_CSUMSHIFT 16 + +#define HME_ERXI_RING 0x4 +#define HME_ERXI_RING_ADDR 0xffffff00 +#define HME_ERXI_RING_OFFSET 0xff + +#define HME_MAC_REG_SIZE 0x1000 + +#define HME_MACI_TXCFG 0x20c +#define HME_MAC_TXCFG_ENABLE 0x1 + +#define HME_MACI_RXCFG 0x30c +#define HME_MAC_RXCFG_ENABLE 0x1 +#define HME_MAC_RXCFG_PMISC 0x40 +#define HME_MAC_RXCFG_HENABLE 0x800 + +#define HME_MACI_MACADDR2 0x318 +#define HME_MACI_MACADDR1 0x31c +#define HME_MACI_MACADDR0 0x320 + +#define HME_MACI_HASHTAB3 0x340 +#define HME_MACI_HASHTAB2 0x344 +#define HME_MACI_HASHTAB1 0x348 +#define HME_MACI_HASHTAB0 0x34c + +#define HME_MIF_REG_SIZE 0x20 + +#define HME_MIFI_FO 0xc +#define HME_MIF_FO_ST 0xc0000000 +#define HME_MIF_FO_ST_SHIFT 30 +#define HME_MIF_FO_OPC 0x30000000 +#define HME_MIF_FO_OPC_SHIFT 28 +#define HME_MIF_FO_PHYAD 0x0f800000 +#define HME_MIF_FO_PHYAD_SHIFT 23 +#define HME_MIF_FO_REGAD 0x007c0000 +#define HME_MIF_FO_REGAD_SHIFT 18 +#define HME_MIF_FO_TAMSB 0x20000 +#define HME_MIF_FO_TALSB 0x10000 +#define HME_MIF_FO_DATA 0xffff + +#define HME_MIFI_CFG 0x10 +#define HME_MIF_CFG_MDI0 0x100 +#define HME_MIF_CFG_MDI1 0x200 + +#define HME_MIFI_IMASK 0x14 + +#define HME_MIFI_STAT 0x18 + + +/* Wired HME PHY addresses */ +#define HME_PHYAD_INTERNAL 1 +#define HME_PHYAD_EXTERNAL 0 + +#define MII_COMMAND_START 0x1 +#define MII_COMMAND_READ 0x2 +#define MII_COMMAND_WRITE 0x1 + +#define TYPE_SUNHME "sunhme" +#define SUNHME(obj) OBJECT_CHECK(SunHMEState, (obj), TYPE_SUNHME) + +/* Maximum size of buffer */ +#define HME_FIFO_SIZE 0x800 + +/* Size of TX/RX descriptor */ +#define HME_DESC_SIZE 0x8 + +#define HME_XD_OWN 0x80000000 +#define HME_XD_OFL 0x40000000 +#define HME_XD_SOP 0x40000000 +#define HME_XD_EOP 0x20000000 +#define HME_XD_RXLENMSK 0x3fff0000 +#define HME_XD_RXLENSHIFT 16 +#define HME_XD_RXCKSUM 0xffff +#define HME_XD_TXLENMSK 0x00001fff +#define HME_XD_TXCKSUM 0x10000000 +#define HME_XD_TXCSSTUFF 0xff00000 +#define HME_XD_TXCSSTUFFSHIFT 20 +#define HME_XD_TXCSSTART 0xfc000 +#define HME_XD_TXCSSTARTSHIFT 14 + +#define HME_MII_REGS_SIZE 0x20 + +typedef struct SunHMEState { + /*< private >*/ + PCIDevice parent_obj; + + NICState *nic; + NICConf conf; + + MemoryRegion hme; + MemoryRegion sebreg; + MemoryRegion etxreg; + MemoryRegion erxreg; + MemoryRegion macreg; + MemoryRegion mifreg; + + uint32_t sebregs[HME_SEB_REG_SIZE >> 2]; + uint32_t etxregs[HME_ETX_REG_SIZE >> 2]; + uint32_t erxregs[HME_ERX_REG_SIZE >> 2]; + uint32_t macregs[HME_MAC_REG_SIZE >> 2]; + uint32_t mifregs[HME_MIF_REG_SIZE >> 2]; + + uint16_t miiregs[HME_MII_REGS_SIZE]; +} SunHMEState; + +static Property sunhme_properties[] = { + DEFINE_NIC_PROPERTIES(SunHMEState, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sunhme_reset_tx(SunHMEState *s) +{ + /* Indicate TX reset complete */ + s->sebregs[HME_SEBI_RESET] &= ~HME_SEB_RESET_ETX; +} + +static void sunhme_reset_rx(SunHMEState *s) +{ + /* Indicate RX reset complete */ + s->sebregs[HME_SEBI_RESET] &= ~HME_SEB_RESET_ERX; +} + +static void sunhme_update_irq(SunHMEState *s) +{ + PCIDevice *d = PCI_DEVICE(s); + int level; + + /* MIF interrupt mask (16-bit) */ + uint32_t mifmask = ~(s->mifregs[HME_MIFI_IMASK >> 2]) & 0xffff; + uint32_t mif = s->mifregs[HME_MIFI_STAT >> 2] & mifmask; + + /* Main SEB interrupt mask (include MIF status from above) */ + uint32_t sebmask = ~(s->sebregs[HME_SEBI_IMASK >> 2]) & + ~HME_SEB_STAT_MIFIRQ; + uint32_t seb = s->sebregs[HME_SEBI_STAT >> 2] & sebmask; + if (mif) { + seb |= HME_SEB_STAT_MIFIRQ; + } + + level = (seb ? 1 : 0); + pci_set_irq(d, level); +} + +static void sunhme_seb_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + + trace_sunhme_seb_write(addr, val); + + /* Handly buggy Linux drivers before 4.13 which have + the wrong offsets for HME_SEBI_STAT and HME_SEBI_IMASK */ + switch (addr) { + case HME_SEBI_STAT_LINUXBUG: + addr = HME_SEBI_STAT; + break; + case HME_SEBI_IMASK_LINUXBUG: + addr = HME_SEBI_IMASK; + break; + default: + break; + } + + switch (addr) { + case HME_SEBI_RESET: + if (val & HME_SEB_RESET_ETX) { + sunhme_reset_tx(s); + } + if (val & HME_SEB_RESET_ERX) { + sunhme_reset_rx(s); + } + val = s->sebregs[HME_SEBI_RESET >> 2]; + break; + } + + s->sebregs[addr >> 2] = val; +} + +static uint64_t sunhme_seb_read(void *opaque, hwaddr addr, + unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + uint64_t val; + + /* Handly buggy Linux drivers before 4.13 which have + the wrong offsets for HME_SEBI_STAT and HME_SEBI_IMASK */ + switch (addr) { + case HME_SEBI_STAT_LINUXBUG: + addr = HME_SEBI_STAT; + break; + case HME_SEBI_IMASK_LINUXBUG: + addr = HME_SEBI_IMASK; + break; + default: + break; + } + + val = s->sebregs[addr >> 2]; + + switch (addr) { + case HME_SEBI_STAT: + /* Autoclear status (except MIF) */ + s->sebregs[HME_SEBI_STAT >> 2] &= HME_SEB_STAT_MIFIRQ; + sunhme_update_irq(s); + break; + } + + trace_sunhme_seb_read(addr, val); + + return val; +} + +static const MemoryRegionOps sunhme_seb_ops = { + .read = sunhme_seb_read, + .write = sunhme_seb_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void sunhme_transmit(SunHMEState *s); + +static void sunhme_etx_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + + trace_sunhme_etx_write(addr, val); + + switch (addr) { + case HME_ETXI_PENDING: + if (val) { + sunhme_transmit(s); + } + break; + } + + s->etxregs[addr >> 2] = val; +} + +static uint64_t sunhme_etx_read(void *opaque, hwaddr addr, + unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + uint64_t val; + + val = s->etxregs[addr >> 2]; + + trace_sunhme_etx_read(addr, val); + + return val; +} + +static const MemoryRegionOps sunhme_etx_ops = { + .read = sunhme_etx_read, + .write = sunhme_etx_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void sunhme_erx_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + + trace_sunhme_erx_write(addr, val); + + s->erxregs[addr >> 2] = val; +} + +static uint64_t sunhme_erx_read(void *opaque, hwaddr addr, + unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + uint64_t val; + + val = s->erxregs[addr >> 2]; + + trace_sunhme_erx_read(addr, val); + + return val; +} + +static const MemoryRegionOps sunhme_erx_ops = { + .read = sunhme_erx_read, + .write = sunhme_erx_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void sunhme_mac_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + + trace_sunhme_mac_write(addr, val); + + s->macregs[addr >> 2] = val; +} + +static uint64_t sunhme_mac_read(void *opaque, hwaddr addr, + unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + uint64_t val; + + val = s->macregs[addr >> 2]; + + trace_sunhme_mac_read(addr, val); + + return val; +} + +static const MemoryRegionOps sunhme_mac_ops = { + .read = sunhme_mac_read, + .write = sunhme_mac_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void sunhme_mii_write(SunHMEState *s, uint8_t reg, uint16_t data) +{ + trace_sunhme_mii_write(reg, data); + + switch (reg) { + case MII_BMCR: + if (data & MII_BMCR_RESET) { + /* Autoclear reset bit, enable auto negotiation */ + data &= ~MII_BMCR_RESET; + data |= MII_BMCR_AUTOEN; + } + if (data & MII_BMCR_ANRESTART) { + /* Autoclear auto negotiation restart */ + data &= ~MII_BMCR_ANRESTART; + + /* Indicate negotiation complete */ + s->miiregs[MII_BMSR] |= MII_BMSR_AN_COMP; + + if (!qemu_get_queue(s->nic)->link_down) { + s->miiregs[MII_ANLPAR] |= MII_ANLPAR_TXFD; + s->miiregs[MII_BMSR] |= MII_BMSR_LINK_ST; + } + } + break; + } + + s->miiregs[reg] = data; +} + +static uint16_t sunhme_mii_read(SunHMEState *s, uint8_t reg) +{ + uint16_t data = s->miiregs[reg]; + + trace_sunhme_mii_read(reg, data); + + return data; +} + +static void sunhme_mif_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + uint8_t cmd, reg; + uint16_t data; + + trace_sunhme_mif_write(addr, val); + + switch (addr) { + case HME_MIFI_CFG: + /* Mask the read-only bits */ + val &= ~(HME_MIF_CFG_MDI0 | HME_MIF_CFG_MDI1); + val |= s->mifregs[HME_MIFI_CFG >> 2] & + (HME_MIF_CFG_MDI0 | HME_MIF_CFG_MDI1); + break; + case HME_MIFI_FO: + /* Detect start of MII command */ + if ((val & HME_MIF_FO_ST) >> HME_MIF_FO_ST_SHIFT + != MII_COMMAND_START) { + val |= HME_MIF_FO_TALSB; + break; + } + + /* Internal phy only */ + if ((val & HME_MIF_FO_PHYAD) >> HME_MIF_FO_PHYAD_SHIFT + != HME_PHYAD_INTERNAL) { + val |= HME_MIF_FO_TALSB; + break; + } + + cmd = (val & HME_MIF_FO_OPC) >> HME_MIF_FO_OPC_SHIFT; + reg = (val & HME_MIF_FO_REGAD) >> HME_MIF_FO_REGAD_SHIFT; + data = (val & HME_MIF_FO_DATA); + + switch (cmd) { + case MII_COMMAND_WRITE: + sunhme_mii_write(s, reg, data); + break; + + case MII_COMMAND_READ: + val &= ~HME_MIF_FO_DATA; + val |= sunhme_mii_read(s, reg); + break; + } + + val |= HME_MIF_FO_TALSB; + break; + } + + s->mifregs[addr >> 2] = val; +} + +static uint64_t sunhme_mif_read(void *opaque, hwaddr addr, + unsigned size) +{ + SunHMEState *s = SUNHME(opaque); + uint64_t val; + + val = s->mifregs[addr >> 2]; + + switch (addr) { + case HME_MIFI_STAT: + /* Autoclear MIF interrupt status */ + s->mifregs[HME_MIFI_STAT >> 2] = 0; + sunhme_update_irq(s); + break; + } + + trace_sunhme_mif_read(addr, val); + + return val; +} + +static const MemoryRegionOps sunhme_mif_ops = { + .read = sunhme_mif_read, + .write = sunhme_mif_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void sunhme_transmit_frame(SunHMEState *s, uint8_t *buf, int size) +{ + qemu_send_packet(qemu_get_queue(s->nic), buf, size); +} + +static inline int sunhme_get_tx_ring_count(SunHMEState *s) +{ + return (s->etxregs[HME_ETXI_RSIZE >> 2] + 1) << 4; +} + +static inline int sunhme_get_tx_ring_nr(SunHMEState *s) +{ + return s->etxregs[HME_ETXI_RING >> 2] & HME_ETXI_RING_OFFSET; +} + +static inline void sunhme_set_tx_ring_nr(SunHMEState *s, int i) +{ + uint32_t ring = s->etxregs[HME_ETXI_RING >> 2] & ~HME_ETXI_RING_OFFSET; + ring |= i & HME_ETXI_RING_OFFSET; + + s->etxregs[HME_ETXI_RING >> 2] = ring; +} + +static void sunhme_transmit(SunHMEState *s) +{ + PCIDevice *d = PCI_DEVICE(s); + dma_addr_t tb, addr; + uint32_t intstatus, status, buffer, sum = 0; + int cr, nr, len, xmit_pos, csum_offset = 0, csum_stuff_offset = 0; + uint16_t csum = 0; + uint8_t xmit_buffer[HME_FIFO_SIZE]; + + tb = s->etxregs[HME_ETXI_RING >> 2] & HME_ETXI_RING_ADDR; + nr = sunhme_get_tx_ring_count(s); + cr = sunhme_get_tx_ring_nr(s); + + pci_dma_read(d, tb + cr * HME_DESC_SIZE, &status, 4); + pci_dma_read(d, tb + cr * HME_DESC_SIZE + 4, &buffer, 4); + + xmit_pos = 0; + while (status & HME_XD_OWN) { + trace_sunhme_tx_desc(buffer, status, cr, nr); + + /* Copy data into transmit buffer */ + addr = buffer; + len = status & HME_XD_TXLENMSK; + + if (xmit_pos + len > HME_FIFO_SIZE) { + len = HME_FIFO_SIZE - xmit_pos; + } + + pci_dma_read(d, addr, &xmit_buffer[xmit_pos], len); + xmit_pos += len; + + /* Detect start of packet for TX checksum */ + if (status & HME_XD_SOP) { + sum = 0; + csum_offset = (status & HME_XD_TXCSSTART) >> HME_XD_TXCSSTARTSHIFT; + csum_stuff_offset = (status & HME_XD_TXCSSTUFF) >> + HME_XD_TXCSSTUFFSHIFT; + } + + if (status & HME_XD_TXCKSUM) { + /* Only start calculation from csum_offset */ + if (xmit_pos - len <= csum_offset && xmit_pos > csum_offset) { + sum += net_checksum_add(xmit_pos - csum_offset, + xmit_buffer + csum_offset); + trace_sunhme_tx_xsum_add(csum_offset, xmit_pos - csum_offset); + } else { + sum += net_checksum_add(len, xmit_buffer + xmit_pos - len); + trace_sunhme_tx_xsum_add(xmit_pos - len, len); + } + } + + /* Detect end of packet for TX checksum */ + if (status & HME_XD_EOP) { + /* Stuff the checksum if required */ + if (status & HME_XD_TXCKSUM) { + csum = net_checksum_finish(sum); + stw_be_p(xmit_buffer + csum_stuff_offset, csum); + trace_sunhme_tx_xsum_stuff(csum, csum_stuff_offset); + } + + if (s->macregs[HME_MACI_TXCFG >> 2] & HME_MAC_TXCFG_ENABLE) { + sunhme_transmit_frame(s, xmit_buffer, xmit_pos); + trace_sunhme_tx_done(xmit_pos); + } + } + + /* Update status */ + status &= ~HME_XD_OWN; + pci_dma_write(d, tb + cr * HME_DESC_SIZE, &status, 4); + + /* Move onto next descriptor */ + cr++; + if (cr >= nr) { + cr = 0; + } + sunhme_set_tx_ring_nr(s, cr); + + pci_dma_read(d, tb + cr * HME_DESC_SIZE, &status, 4); + pci_dma_read(d, tb + cr * HME_DESC_SIZE + 4, &buffer, 4); + + /* Indicate TX complete */ + intstatus = s->sebregs[HME_SEBI_STAT >> 2]; + intstatus |= HME_SEB_STAT_HOSTTOTX; + s->sebregs[HME_SEBI_STAT >> 2] = intstatus; + + /* Autoclear TX pending */ + s->etxregs[HME_ETXI_PENDING >> 2] = 0; + + sunhme_update_irq(s); + } + + /* TX FIFO now clear */ + intstatus = s->sebregs[HME_SEBI_STAT >> 2]; + intstatus |= HME_SEB_STAT_TXALL; + s->sebregs[HME_SEBI_STAT >> 2] = intstatus; + sunhme_update_irq(s); +} + +static int sunhme_can_receive(NetClientState *nc) +{ + SunHMEState *s = qemu_get_nic_opaque(nc); + + return s->macregs[HME_MAC_RXCFG_ENABLE >> 2] & HME_MAC_RXCFG_ENABLE; +} + +static void sunhme_link_status_changed(NetClientState *nc) +{ + SunHMEState *s = qemu_get_nic_opaque(nc); + + if (nc->link_down) { + s->miiregs[MII_ANLPAR] &= ~MII_ANLPAR_TXFD; + s->miiregs[MII_BMSR] &= ~MII_BMSR_LINK_ST; + } else { + s->miiregs[MII_ANLPAR] |= MII_ANLPAR_TXFD; + s->miiregs[MII_BMSR] |= MII_BMSR_LINK_ST; + } + + /* Exact bits unknown */ + s->mifregs[HME_MIFI_STAT >> 2] = 0xffff; + sunhme_update_irq(s); +} + +static inline int sunhme_get_rx_ring_count(SunHMEState *s) +{ + uint32_t rings = (s->erxregs[HME_ERXI_CFG >> 2] & HME_ERX_CFG_RINGSIZE) + >> HME_ERX_CFG_RINGSIZE_SHIFT; + + switch (rings) { + case 0: + return 32; + case 1: + return 64; + case 2: + return 128; + case 3: + return 256; + } + + return 0; +} + +static inline int sunhme_get_rx_ring_nr(SunHMEState *s) +{ + return s->erxregs[HME_ERXI_RING >> 2] & HME_ERXI_RING_OFFSET; +} + +static inline void sunhme_set_rx_ring_nr(SunHMEState *s, int i) +{ + uint32_t ring = s->erxregs[HME_ERXI_RING >> 2] & ~HME_ERXI_RING_OFFSET; + ring |= i & HME_ERXI_RING_OFFSET; + + s->erxregs[HME_ERXI_RING >> 2] = ring; +} + +#define POLYNOMIAL_LE 0xedb88320 +static uint32_t sunhme_crc32_le(const uint8_t *p, int len) +{ + uint32_t crc; + int carry, i, j; + uint8_t b; + + crc = 0xffffffff; + for (i = 0; i < len; i++) { + b = *p++; + for (j = 0; j < 8; j++) { + carry = (crc & 0x1) ^ (b & 0x01); + crc >>= 1; + b >>= 1; + if (carry) { + crc = crc ^ POLYNOMIAL_LE; + } + } + } + + return crc; +} + +#define MIN_BUF_SIZE 60 + +static ssize_t sunhme_receive(NetClientState *nc, const uint8_t *buf, + size_t size) +{ + SunHMEState *s = qemu_get_nic_opaque(nc); + PCIDevice *d = PCI_DEVICE(s); + dma_addr_t rb, addr; + uint32_t intstatus, status, buffer, buffersize, sum; + uint16_t csum; + uint8_t buf1[60]; + int nr, cr, len, rxoffset, csum_offset; + + trace_sunhme_rx_incoming(size); + + /* Do nothing if MAC RX disabled */ + if (!(s->macregs[HME_MACI_RXCFG >> 2] & HME_MAC_RXCFG_ENABLE)) { + return -1; + } + + trace_sunhme_rx_filter_destmac(buf[0], buf[1], buf[2], + buf[3], buf[4], buf[5]); + + /* Check destination MAC address */ + if (!(s->macregs[HME_MACI_RXCFG >> 2] & HME_MAC_RXCFG_PMISC)) { + /* Try and match local MAC address */ + if (((s->macregs[HME_MACI_MACADDR0 >> 2] & 0xff00) >> 8) == buf[0] && + (s->macregs[HME_MACI_MACADDR0 >> 2] & 0xff) == buf[1] && + ((s->macregs[HME_MACI_MACADDR1 >> 2] & 0xff00) >> 8) == buf[2] && + (s->macregs[HME_MACI_MACADDR1 >> 2] & 0xff) == buf[3] && + ((s->macregs[HME_MACI_MACADDR2 >> 2] & 0xff00) >> 8) == buf[4] && + (s->macregs[HME_MACI_MACADDR2 >> 2] & 0xff) == buf[5]) { + /* Matched local MAC address */ + trace_sunhme_rx_filter_local_match(); + } else if (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0xff && + buf[3] == 0xff && buf[4] == 0xff && buf[5] == 0xff) { + /* Matched broadcast address */ + trace_sunhme_rx_filter_bcast_match(); + } else if (s->macregs[HME_MACI_RXCFG >> 2] & HME_MAC_RXCFG_HENABLE) { + /* Didn't match local address, check hash filter */ + int mcast_idx = sunhme_crc32_le(buf, 6) >> 26; + if (!(s->macregs[(HME_MACI_HASHTAB0 >> 2) - (mcast_idx >> 4)] & + (1 << (mcast_idx & 0xf)))) { + /* Didn't match hash filter */ + trace_sunhme_rx_filter_hash_nomatch(); + trace_sunhme_rx_filter_reject(); + return 0; + } else { + trace_sunhme_rx_filter_hash_match(); + } + } else { + /* Not for us */ + trace_sunhme_rx_filter_reject(); + return 0; + } + } else { + trace_sunhme_rx_filter_promisc_match(); + } + + trace_sunhme_rx_filter_accept(); + + /* If too small buffer, then expand it */ + if (size < MIN_BUF_SIZE) { + memcpy(buf1, buf, size); + memset(buf1 + size, 0, MIN_BUF_SIZE - size); + buf = buf1; + size = MIN_BUF_SIZE; + } + + rb = s->erxregs[HME_ERXI_RING >> 2] & HME_ERXI_RING_ADDR; + nr = sunhme_get_rx_ring_count(s); + cr = sunhme_get_rx_ring_nr(s); + + pci_dma_read(d, rb + cr * HME_DESC_SIZE, &status, 4); + pci_dma_read(d, rb + cr * HME_DESC_SIZE + 4, &buffer, 4); + + rxoffset = (s->erxregs[HME_ERXI_CFG >> 2] & HME_ERX_CFG_BYTEOFFSET) >> + HME_ERX_CFG_BYTEOFFSET_SHIFT; + + addr = buffer + rxoffset; + buffersize = (status & HME_XD_RXLENMSK) >> HME_XD_RXLENSHIFT; + + /* Detect receive overflow */ + len = size; + if (size > buffersize) { + status |= HME_XD_OFL; + len = buffersize; + } + + pci_dma_write(d, addr, buf, len); + + trace_sunhme_rx_desc(buffer, rxoffset, status, len, cr, nr); + + /* Calculate the receive checksum */ + csum_offset = (s->erxregs[HME_ERXI_CFG >> 2] & HME_ERX_CFG_CSUMSTART) >> + HME_ERX_CFG_CSUMSHIFT << 1; + sum = 0; + sum += net_checksum_add(len - csum_offset, (uint8_t *)buf + csum_offset); + csum = net_checksum_finish(sum); + + trace_sunhme_rx_xsum_calc(csum); + + /* Update status */ + status &= ~HME_XD_OWN; + status &= ~HME_XD_RXLENMSK; + status |= len << HME_XD_RXLENSHIFT; + status &= ~HME_XD_RXCKSUM; + status |= csum; + + pci_dma_write(d, rb + cr * HME_DESC_SIZE, &status, 4); + + cr++; + if (cr >= nr) { + cr = 0; + } + + sunhme_set_rx_ring_nr(s, cr); + + /* Indicate RX complete */ + intstatus = s->sebregs[HME_SEBI_STAT >> 2]; + intstatus |= HME_SEB_STAT_RXTOHOST; + s->sebregs[HME_SEBI_STAT >> 2] = intstatus; + + sunhme_update_irq(s); + + return len; +} + +static NetClientInfo net_sunhme_info = { + .type = NET_CLIENT_DRIVER_NIC, + .size = sizeof(NICState), + .can_receive = sunhme_can_receive, + .receive = sunhme_receive, + .link_status_changed = sunhme_link_status_changed, +}; + +static void sunhme_realize(PCIDevice *pci_dev, Error **errp) +{ + SunHMEState *s = SUNHME(pci_dev); + DeviceState *d = DEVICE(pci_dev); + uint8_t *pci_conf; + + pci_conf = pci_dev->config; + pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */ + + memory_region_init(&s->hme, OBJECT(pci_dev), "sunhme", HME_REG_SIZE); + pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->hme); + + memory_region_init_io(&s->sebreg, OBJECT(pci_dev), &sunhme_seb_ops, s, + "sunhme.seb", HME_SEB_REG_SIZE); + memory_region_add_subregion(&s->hme, 0, &s->sebreg); + + memory_region_init_io(&s->etxreg, OBJECT(pci_dev), &sunhme_etx_ops, s, + "sunhme.etx", HME_ETX_REG_SIZE); + memory_region_add_subregion(&s->hme, 0x2000, &s->etxreg); + + memory_region_init_io(&s->erxreg, OBJECT(pci_dev), &sunhme_erx_ops, s, + "sunhme.erx", HME_ERX_REG_SIZE); + memory_region_add_subregion(&s->hme, 0x4000, &s->erxreg); + + memory_region_init_io(&s->macreg, OBJECT(pci_dev), &sunhme_mac_ops, s, + "sunhme.mac", HME_MAC_REG_SIZE); + memory_region_add_subregion(&s->hme, 0x6000, &s->macreg); + + memory_region_init_io(&s->mifreg, OBJECT(pci_dev), &sunhme_mif_ops, s, + "sunhme.mif", HME_MIF_REG_SIZE); + memory_region_add_subregion(&s->hme, 0x7000, &s->mifreg); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(&net_sunhme_info, &s->conf, + object_get_typename(OBJECT(d)), d->id, s); + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); +} + +static void sunhme_instance_init(Object *obj) +{ + SunHMEState *s = SUNHME(obj); + + device_add_bootindex_property(obj, &s->conf.bootindex, + "bootindex", "/ethernet-phy@0", + DEVICE(obj), NULL); +} + +static void sunhme_reset(DeviceState *ds) +{ + SunHMEState *s = SUNHME(ds); + + /* Configure internal transceiver */ + s->mifregs[HME_MIFI_CFG >> 2] |= HME_MIF_CFG_MDI0; + + /* Advetise auto, 100Mbps FD */ + s->miiregs[MII_ANAR] = MII_ANAR_TXFD; + s->miiregs[MII_BMSR] = MII_BMSR_AUTONEG | MII_BMSR_100TX_FD | + MII_BMSR_AN_COMP; + + if (!qemu_get_queue(s->nic)->link_down) { + s->miiregs[MII_ANLPAR] |= MII_ANLPAR_TXFD; + s->miiregs[MII_BMSR] |= MII_BMSR_LINK_ST; + } + + /* Set manufacturer */ + s->miiregs[MII_PHYID1] = DP83840_PHYID1; + s->miiregs[MII_PHYID2] = DP83840_PHYID2; + + /* Configure default interrupt mask */ + s->mifregs[HME_MIFI_IMASK >> 2] = 0xffff; + s->sebregs[HME_SEBI_IMASK >> 2] = 0xff7fffff; +} + +static const VMStateDescription vmstate_hme = { + .name = "sunhme", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(parent_obj, SunHMEState), + VMSTATE_MACADDR(conf.macaddr, SunHMEState), + VMSTATE_UINT32_ARRAY(sebregs, SunHMEState, (HME_SEB_REG_SIZE >> 2)), + VMSTATE_UINT32_ARRAY(etxregs, SunHMEState, (HME_ETX_REG_SIZE >> 2)), + VMSTATE_UINT32_ARRAY(erxregs, SunHMEState, (HME_ERX_REG_SIZE >> 2)), + VMSTATE_UINT32_ARRAY(macregs, SunHMEState, (HME_MAC_REG_SIZE >> 2)), + VMSTATE_UINT32_ARRAY(mifregs, SunHMEState, (HME_MIF_REG_SIZE >> 2)), + VMSTATE_UINT16_ARRAY(miiregs, SunHMEState, HME_MII_REGS_SIZE), + VMSTATE_END_OF_LIST() + } +}; + +static void sunhme_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = sunhme_realize; + k->vendor_id = PCI_VENDOR_ID_SUN; + k->device_id = PCI_DEVICE_ID_SUN_HME; + k->class_id = PCI_CLASS_NETWORK_ETHERNET; + dc->vmsd = &vmstate_hme; + dc->reset = sunhme_reset; + dc->props = sunhme_properties; + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); +} + +static const TypeInfo sunhme_info = { + .name = TYPE_SUNHME, + .parent = TYPE_PCI_DEVICE, + .class_init = sunhme_class_init, + .instance_size = sizeof(SunHMEState), + .instance_init = sunhme_instance_init, +}; + +static void sunhme_register_types(void) +{ + type_register_static(&sunhme_info); +} + +type_init(sunhme_register_types) diff --git a/hw/net/trace-events b/hw/net/trace-events index 5f816ef58e..45c4e9fba0 100644 --- a/hw/net/trace-events +++ b/hw/net/trace-events @@ -322,3 +322,32 @@ sungem_mmio_mif_write(uint64_t addr, uint64_t val) "MMIO mif write to 0x%"PRIx64 sungem_mmio_mif_read(uint64_t addr, uint64_t val) "MMIO mif read from 0x%"PRIx64" val=0x%"PRIx64 sungem_mmio_pcs_write(uint64_t addr, uint64_t val) "MMIO pcs write to 0x%"PRIx64" val=0x%"PRIx64 sungem_mmio_pcs_read(uint64_t addr, uint64_t val) "MMIO pcs read from 0x%"PRIx64" val=0x%"PRIx64 + +# hw/net/sunhme.c +sunhme_seb_write(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_seb_read(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_etx_write(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_etx_read(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_erx_write(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_erx_read(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_mac_write(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_mac_read(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_mii_write(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_mii_read(uint8_t addr, uint16_t value) "addr 0x%x value 0x%x" +sunhme_mif_write(uint8_t addr, uint16_t value) "addr 0x%x value 0x%x" +sunhme_mif_read(uint64_t addr, uint64_t value) "addr 0x%"PRIx64" value 0x%"PRIx64 +sunhme_tx_desc(uint64_t buffer, uint32_t status, int cr, int nr) "addr 0x%"PRIx64" status 0x%"PRIx32 " (ring %d/%d)" +sunhme_tx_xsum_add(int offset, int len) "adding xsum at offset %d, len %d" +sunhme_tx_xsum_stuff(uint16_t xsum, int offset) "stuffing xsum 0x%x at offset %d" +sunhme_tx_done(int len) "successfully transmitted frame with len %d" +sunhme_rx_incoming(size_t len) "received incoming frame with len %zu" +sunhme_rx_filter_destmac(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4, uint8_t b5) "received frame for MAC: %02x:%02x:%02x:%02x:%02x:%02x" +sunhme_rx_filter_local_match(void) "incoming frame matches local MAC address" +sunhme_rx_filter_bcast_match(void) "incoming frame matches broadcast MAC address" +sunhme_rx_filter_hash_nomatch(void) "incoming MAC address not in hash table" +sunhme_rx_filter_hash_match(void) "incoming MAC address found in hash table" +sunhme_rx_filter_promisc_match(void) "incoming frame accepted due to promiscuous mode" +sunhme_rx_filter_reject(void) "rejecting incoming frame" +sunhme_rx_filter_accept(void) "accepting incoming frame" +sunhme_rx_desc(uint32_t addr, int offset, uint32_t status, int len, int cr, int nr) "addr 0x%"PRIx32"(+0x%x) status 0x%"PRIx32 " len %d (ring %d/%d)" +sunhme_rx_xsum_calc(uint16_t xsum) "calculated incoming xsum as 0x%x" diff --git a/hw/sparc64/sun4u.c b/hw/sparc64/sun4u.c index 5e59269adc..c3280aaf38 100644 --- a/hw/sparc64/sun4u.c +++ b/hw/sparc64/sun4u.c @@ -427,7 +427,7 @@ static void sun4uv_init(MemoryRegion *address_space_mem, unsigned int i; uint64_t initrd_addr, initrd_size, kernel_addr, kernel_size, kernel_entry; PCIBus *pci_bus, *pci_busA, *pci_busB; - PCIDevice *ebus; + PCIDevice *ebus, *pci_dev; ISABus *isa_bus; SysBusDevice *s; qemu_irq *ivec_irqs, *pbm_irqs; @@ -435,6 +435,8 @@ static void sun4uv_init(MemoryRegion *address_space_mem, DriveInfo *fd[MAX_FD]; DeviceState *dev; FWCfgState *fw_cfg; + NICInfo *nd; + int onboard_nic_idx; /* init CPUs */ cpu = sparc64_cpu_devinit(machine->cpu_model, hwdef->default_cpu_model, @@ -464,8 +466,23 @@ static void sun4uv_init(MemoryRegion *address_space_mem, serial_hds_isa_init(isa_bus, i, MAX_SERIAL_PORTS); parallel_hds_isa_init(isa_bus, MAX_PARALLEL_PORTS); - for(i = 0; i < nb_nics; i++) - pci_nic_init_nofail(&nd_table[i], pci_bus, "ne2k_pci", NULL); + onboard_nic_idx = -1; + for (i = 0; i < nb_nics; i++) { + nd = &nd_table[i]; + + if (onboard_nic_idx == -1 && + (!nd->model || strcmp(nd->model, "sunhme") == 0)) { + pci_dev = pci_create(pci_bus, -1, "sunhme"); + dev = &pci_dev->qdev; + qdev_set_nic_properties(dev, nd); + qdev_init_nofail(dev); + + onboard_nic_idx = i; + } else { + pci_nic_init_nofail(nd, pci_bus, "ne2k_pci", NULL); + } + } + onboard_nic_idx = MAX(onboard_nic_idx, 0); ide_drive_get(hd, ARRAY_SIZE(hd)); @@ -510,7 +527,7 @@ static void sun4uv_init(MemoryRegion *address_space_mem, /* XXX: need an option to load a NVRAM image */ 0, graphic_width, graphic_height, graphic_depth, - (uint8_t *)&nd_table[0].macaddr); + (uint8_t *)&nd_table[onboard_nic_idx].macaddr); dev = qdev_create(NULL, TYPE_FW_CFG_IO); qdev_prop_set_bit(dev, "dma_enabled", false); diff --git a/hw/ssi/Makefile.objs b/hw/ssi/Makefile.objs index 487add2879..f5bcc65fe7 100644 --- a/hw/ssi/Makefile.objs +++ b/hw/ssi/Makefile.objs @@ -4,6 +4,7 @@ common-obj-$(CONFIG_XILINX_SPI) += xilinx_spi.o common-obj-$(CONFIG_XILINX_SPIPS) += xilinx_spips.o common-obj-$(CONFIG_ASPEED_SOC) += aspeed_smc.o common-obj-$(CONFIG_STM32F2XX_SPI) += stm32f2xx_spi.o +common-obj-$(CONFIG_MSF2) += mss-spi.o obj-$(CONFIG_OMAP) += omap_spi.o obj-$(CONFIG_IMX) += imx_spi.o diff --git a/hw/ssi/mss-spi.c b/hw/ssi/mss-spi.c new file mode 100644 index 0000000000..5a8e308e69 --- /dev/null +++ b/hw/ssi/mss-spi.c @@ -0,0 +1,404 @@ +/* + * Block model of SPI controller present in + * Microsemi's SmartFusion2 and SmartFusion SoCs. + * + * Copyright (C) 2017 Subbaraya Sundeep <sundeep.lkml@gmail.com> + * + * 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/ssi/mss-spi.h" +#include "qemu/log.h" + +#ifndef MSS_SPI_ERR_DEBUG +#define MSS_SPI_ERR_DEBUG 0 +#endif + +#define DB_PRINT_L(lvl, fmt, args...) do { \ + if (MSS_SPI_ERR_DEBUG >= lvl) { \ + qemu_log("%s: " fmt "\n", __func__, ## args); \ + } \ +} while (0); + +#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args) + +#define FIFO_CAPACITY 32 + +#define R_SPI_CONTROL 0 +#define R_SPI_DFSIZE 1 +#define R_SPI_STATUS 2 +#define R_SPI_INTCLR 3 +#define R_SPI_RX 4 +#define R_SPI_TX 5 +#define R_SPI_CLKGEN 6 +#define R_SPI_SS 7 +#define R_SPI_MIS 8 +#define R_SPI_RIS 9 + +#define S_TXDONE (1 << 0) +#define S_RXRDY (1 << 1) +#define S_RXCHOVRF (1 << 2) +#define S_RXFIFOFUL (1 << 4) +#define S_RXFIFOFULNXT (1 << 5) +#define S_RXFIFOEMP (1 << 6) +#define S_RXFIFOEMPNXT (1 << 7) +#define S_TXFIFOFUL (1 << 8) +#define S_TXFIFOFULNXT (1 << 9) +#define S_TXFIFOEMP (1 << 10) +#define S_TXFIFOEMPNXT (1 << 11) +#define S_FRAMESTART (1 << 12) +#define S_SSEL (1 << 13) +#define S_ACTIVE (1 << 14) + +#define C_ENABLE (1 << 0) +#define C_MODE (1 << 1) +#define C_INTRXDATA (1 << 4) +#define C_INTTXDATA (1 << 5) +#define C_INTRXOVRFLO (1 << 6) +#define C_SPS (1 << 26) +#define C_BIGFIFO (1 << 29) +#define C_RESET (1 << 31) + +#define FRAMESZ_MASK 0x1F +#define FMCOUNT_MASK 0x00FFFF00 +#define FMCOUNT_SHIFT 8 + +static void txfifo_reset(MSSSpiState *s) +{ + fifo32_reset(&s->tx_fifo); + + s->regs[R_SPI_STATUS] &= ~S_TXFIFOFUL; + s->regs[R_SPI_STATUS] |= S_TXFIFOEMP; +} + +static void rxfifo_reset(MSSSpiState *s) +{ + fifo32_reset(&s->rx_fifo); + + s->regs[R_SPI_STATUS] &= ~S_RXFIFOFUL; + s->regs[R_SPI_STATUS] |= S_RXFIFOEMP; +} + +static void set_fifodepth(MSSSpiState *s) +{ + unsigned int size = s->regs[R_SPI_DFSIZE] & FRAMESZ_MASK; + + if (size <= 8) { + s->fifo_depth = 32; + } else if (size <= 16) { + s->fifo_depth = 16; + } else if (size <= 32) { + s->fifo_depth = 8; + } else { + s->fifo_depth = 4; + } +} + +static void update_mis(MSSSpiState *s) +{ + uint32_t reg = s->regs[R_SPI_CONTROL]; + uint32_t tmp; + + /* + * form the Control register interrupt enable bits + * same as RIS, MIS and Interrupt clear registers for simplicity + */ + tmp = ((reg & C_INTRXOVRFLO) >> 4) | ((reg & C_INTRXDATA) >> 3) | + ((reg & C_INTTXDATA) >> 5); + s->regs[R_SPI_MIS] |= tmp & s->regs[R_SPI_RIS]; +} + +static void spi_update_irq(MSSSpiState *s) +{ + int irq; + + update_mis(s); + irq = !!(s->regs[R_SPI_MIS]); + + qemu_set_irq(s->irq, irq); +} + +static void mss_spi_reset(DeviceState *d) +{ + MSSSpiState *s = MSS_SPI(d); + + memset(s->regs, 0, sizeof s->regs); + s->regs[R_SPI_CONTROL] = 0x80000102; + s->regs[R_SPI_DFSIZE] = 0x4; + s->regs[R_SPI_STATUS] = S_SSEL | S_TXFIFOEMP | S_RXFIFOEMP; + s->regs[R_SPI_CLKGEN] = 0x7; + s->regs[R_SPI_RIS] = 0x0; + + s->fifo_depth = 4; + s->frame_count = 1; + s->enabled = false; + + rxfifo_reset(s); + txfifo_reset(s); +} + +static uint64_t +spi_read(void *opaque, hwaddr addr, unsigned int size) +{ + MSSSpiState *s = opaque; + uint32_t ret = 0; + + addr >>= 2; + switch (addr) { + case R_SPI_RX: + s->regs[R_SPI_STATUS] &= ~S_RXFIFOFUL; + s->regs[R_SPI_STATUS] &= ~S_RXCHOVRF; + ret = fifo32_pop(&s->rx_fifo); + if (fifo32_is_empty(&s->rx_fifo)) { + s->regs[R_SPI_STATUS] |= S_RXFIFOEMP; + } + break; + + case R_SPI_MIS: + update_mis(s); + ret = s->regs[R_SPI_MIS]; + break; + + default: + if (addr < ARRAY_SIZE(s->regs)) { + ret = s->regs[addr]; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + addr * 4); + return ret; + } + break; + } + + DB_PRINT("addr=0x%" HWADDR_PRIx " = 0x%" PRIx32, addr * 4, ret); + spi_update_irq(s); + return ret; +} + +static void assert_cs(MSSSpiState *s) +{ + qemu_set_irq(s->cs_line, 0); +} + +static void deassert_cs(MSSSpiState *s) +{ + qemu_set_irq(s->cs_line, 1); +} + +static void spi_flush_txfifo(MSSSpiState *s) +{ + uint32_t tx; + uint32_t rx; + bool sps = !!(s->regs[R_SPI_CONTROL] & C_SPS); + + /* + * Chip Select(CS) is automatically controlled by this controller. + * If SPS bit is set in Control register then CS is asserted + * until all the frames set in frame count of Control register are + * transferred. If SPS is not set then CS pulses between frames. + * Note that Slave Select register specifies which of the CS line + * has to be controlled automatically by controller. Bits SS[7:1] are for + * masters in FPGA fabric since we model only Microcontroller subsystem + * of Smartfusion2 we control only one CS(SS[0]) line. + */ + while (!fifo32_is_empty(&s->tx_fifo) && s->frame_count) { + assert_cs(s); + + s->regs[R_SPI_STATUS] &= ~(S_TXDONE | S_RXRDY); + + tx = fifo32_pop(&s->tx_fifo); + DB_PRINT("data tx:0x%" PRIx32, tx); + rx = ssi_transfer(s->spi, tx); + DB_PRINT("data rx:0x%" PRIx32, rx); + + if (fifo32_num_used(&s->rx_fifo) == s->fifo_depth) { + s->regs[R_SPI_STATUS] |= S_RXCHOVRF; + s->regs[R_SPI_RIS] |= S_RXCHOVRF; + } else { + fifo32_push(&s->rx_fifo, rx); + s->regs[R_SPI_STATUS] &= ~S_RXFIFOEMP; + if (fifo32_num_used(&s->rx_fifo) == (s->fifo_depth - 1)) { + s->regs[R_SPI_STATUS] |= S_RXFIFOFULNXT; + } else if (fifo32_num_used(&s->rx_fifo) == s->fifo_depth) { + s->regs[R_SPI_STATUS] |= S_RXFIFOFUL; + } + } + s->frame_count--; + if (!sps) { + deassert_cs(s); + } + } + + if (!s->frame_count) { + s->frame_count = (s->regs[R_SPI_CONTROL] & FMCOUNT_MASK) >> + FMCOUNT_SHIFT; + deassert_cs(s); + s->regs[R_SPI_RIS] |= S_TXDONE | S_RXRDY; + s->regs[R_SPI_STATUS] |= S_TXDONE | S_RXRDY; + } +} + +static void spi_write(void *opaque, hwaddr addr, + uint64_t val64, unsigned int size) +{ + MSSSpiState *s = opaque; + uint32_t value = val64; + + DB_PRINT("addr=0x%" HWADDR_PRIx " =0x%" PRIx32, addr, value); + addr >>= 2; + + switch (addr) { + case R_SPI_TX: + /* adding to already full FIFO */ + if (fifo32_num_used(&s->tx_fifo) == s->fifo_depth) { + break; + } + s->regs[R_SPI_STATUS] &= ~S_TXFIFOEMP; + fifo32_push(&s->tx_fifo, value); + if (fifo32_num_used(&s->tx_fifo) == (s->fifo_depth - 1)) { + s->regs[R_SPI_STATUS] |= S_TXFIFOFULNXT; + } else if (fifo32_num_used(&s->tx_fifo) == s->fifo_depth) { + s->regs[R_SPI_STATUS] |= S_TXFIFOFUL; + } + if (s->enabled) { + spi_flush_txfifo(s); + } + break; + + case R_SPI_CONTROL: + s->regs[R_SPI_CONTROL] = value; + if (value & C_BIGFIFO) { + set_fifodepth(s); + } else { + s->fifo_depth = 4; + } + s->enabled = value & C_ENABLE; + s->frame_count = (value & FMCOUNT_MASK) >> FMCOUNT_SHIFT; + if (value & C_RESET) { + mss_spi_reset(DEVICE(s)); + } + break; + + case R_SPI_DFSIZE: + if (s->enabled) { + break; + } + s->regs[R_SPI_DFSIZE] = value; + break; + + case R_SPI_INTCLR: + s->regs[R_SPI_INTCLR] = value; + if (value & S_TXDONE) { + s->regs[R_SPI_RIS] &= ~S_TXDONE; + } + if (value & S_RXRDY) { + s->regs[R_SPI_RIS] &= ~S_RXRDY; + } + if (value & S_RXCHOVRF) { + s->regs[R_SPI_RIS] &= ~S_RXCHOVRF; + } + break; + + case R_SPI_MIS: + case R_SPI_STATUS: + case R_SPI_RIS: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Write to read only register 0x%" HWADDR_PRIx "\n", + __func__, addr * 4); + break; + + default: + if (addr < ARRAY_SIZE(s->regs)) { + s->regs[addr] = value; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + addr * 4); + } + break; + } + + spi_update_irq(s); +} + +static const MemoryRegionOps spi_ops = { + .read = spi_read, + .write = spi_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4 + } +}; + +static void mss_spi_realize(DeviceState *dev, Error **errp) +{ + MSSSpiState *s = MSS_SPI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + s->spi = ssi_create_bus(dev, "spi"); + + sysbus_init_irq(sbd, &s->irq); + ssi_auto_connect_slaves(dev, &s->cs_line, s->spi); + sysbus_init_irq(sbd, &s->cs_line); + + memory_region_init_io(&s->mmio, OBJECT(s), &spi_ops, s, + TYPE_MSS_SPI, R_SPI_MAX * 4); + sysbus_init_mmio(sbd, &s->mmio); + + fifo32_create(&s->tx_fifo, FIFO_CAPACITY); + fifo32_create(&s->rx_fifo, FIFO_CAPACITY); +} + +static const VMStateDescription vmstate_mss_spi = { + .name = TYPE_MSS_SPI, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_FIFO32(tx_fifo, MSSSpiState), + VMSTATE_FIFO32(rx_fifo, MSSSpiState), + VMSTATE_UINT32_ARRAY(regs, MSSSpiState, R_SPI_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void mss_spi_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = mss_spi_realize; + dc->reset = mss_spi_reset; + dc->vmsd = &vmstate_mss_spi; +} + +static const TypeInfo mss_spi_info = { + .name = TYPE_MSS_SPI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MSSSpiState), + .class_init = mss_spi_class_init, +}; + +static void mss_spi_register_types(void) +{ + type_register_static(&mss_spi_info); +} + +type_init(mss_spi_register_types) diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs index 15cce1c531..8c19eac3b6 100644 --- a/hw/timer/Makefile.objs +++ b/hw/timer/Makefile.objs @@ -42,3 +42,4 @@ common-obj-$(CONFIG_ASPEED_SOC) += aspeed_timer.o common-obj-$(CONFIG_SUN4V_RTC) += sun4v-rtc.o common-obj-$(CONFIG_CMSDK_APB_TIMER) += cmsdk-apb-timer.o +common-obj-$(CONFIG_MSF2) += mss-timer.o diff --git a/hw/timer/mss-timer.c b/hw/timer/mss-timer.c new file mode 100644 index 0000000000..60f1213a3b --- /dev/null +++ b/hw/timer/mss-timer.c @@ -0,0 +1,289 @@ +/* + * Block model of System timer present in + * Microsemi's SmartFusion2 and SmartFusion SoCs. + * + * Copyright (c) 2017 Subbaraya Sundeep <sundeep.lkml@gmail.com>. + * + * 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 "qemu/main-loop.h" +#include "qemu/log.h" +#include "hw/timer/mss-timer.h" + +#ifndef MSS_TIMER_ERR_DEBUG +#define MSS_TIMER_ERR_DEBUG 0 +#endif + +#define DB_PRINT_L(lvl, fmt, args...) do { \ + if (MSS_TIMER_ERR_DEBUG >= lvl) { \ + qemu_log("%s: " fmt "\n", __func__, ## args); \ + } \ +} while (0); + +#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args) + +#define R_TIM_VAL 0 +#define R_TIM_LOADVAL 1 +#define R_TIM_BGLOADVAL 2 +#define R_TIM_CTRL 3 +#define R_TIM_RIS 4 +#define R_TIM_MIS 5 + +#define TIMER_CTRL_ENBL (1 << 0) +#define TIMER_CTRL_ONESHOT (1 << 1) +#define TIMER_CTRL_INTR (1 << 2) +#define TIMER_RIS_ACK (1 << 0) +#define TIMER_RST_CLR (1 << 6) +#define TIMER_MODE (1 << 0) + +static void timer_update_irq(struct Msf2Timer *st) +{ + bool isr, ier; + + isr = !!(st->regs[R_TIM_RIS] & TIMER_RIS_ACK); + ier = !!(st->regs[R_TIM_CTRL] & TIMER_CTRL_INTR); + qemu_set_irq(st->irq, (ier && isr)); +} + +static void timer_update(struct Msf2Timer *st) +{ + uint64_t count; + + if (!(st->regs[R_TIM_CTRL] & TIMER_CTRL_ENBL)) { + ptimer_stop(st->ptimer); + return; + } + + count = st->regs[R_TIM_LOADVAL]; + ptimer_set_limit(st->ptimer, count, 1); + ptimer_run(st->ptimer, 1); +} + +static uint64_t +timer_read(void *opaque, hwaddr offset, unsigned int size) +{ + MSSTimerState *t = opaque; + hwaddr addr; + struct Msf2Timer *st; + uint32_t ret = 0; + int timer = 0; + int isr; + int ier; + + addr = offset >> 2; + /* + * Two independent timers has same base address. + * Based on address passed figure out which timer is being used. + */ + if ((addr >= R_TIM1_MAX) && (addr < NUM_TIMERS * R_TIM1_MAX)) { + timer = 1; + addr -= R_TIM1_MAX; + } + + st = &t->timers[timer]; + + switch (addr) { + case R_TIM_VAL: + ret = ptimer_get_count(st->ptimer); + break; + + case R_TIM_MIS: + isr = !!(st->regs[R_TIM_RIS] & TIMER_RIS_ACK); + ier = !!(st->regs[R_TIM_CTRL] & TIMER_CTRL_INTR); + ret = ier & isr; + break; + + default: + if (addr < R_TIM1_MAX) { + ret = st->regs[addr]; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + TYPE_MSS_TIMER": 64-bit mode not supported\n"); + return ret; + } + break; + } + + DB_PRINT("timer=%d 0x%" HWADDR_PRIx "=0x%" PRIx32, timer, offset, + ret); + return ret; +} + +static void +timer_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned int size) +{ + MSSTimerState *t = opaque; + hwaddr addr; + struct Msf2Timer *st; + int timer = 0; + uint32_t value = val64; + + addr = offset >> 2; + /* + * Two independent timers has same base address. + * Based on addr passed figure out which timer is being used. + */ + if ((addr >= R_TIM1_MAX) && (addr < NUM_TIMERS * R_TIM1_MAX)) { + timer = 1; + addr -= R_TIM1_MAX; + } + + st = &t->timers[timer]; + + DB_PRINT("addr=0x%" HWADDR_PRIx " val=0x%" PRIx32 " (timer=%d)", offset, + value, timer); + + switch (addr) { + case R_TIM_CTRL: + st->regs[R_TIM_CTRL] = value; + timer_update(st); + break; + + case R_TIM_RIS: + if (value & TIMER_RIS_ACK) { + st->regs[R_TIM_RIS] &= ~TIMER_RIS_ACK; + } + break; + + case R_TIM_LOADVAL: + st->regs[R_TIM_LOADVAL] = value; + if (st->regs[R_TIM_CTRL] & TIMER_CTRL_ENBL) { + timer_update(st); + } + break; + + case R_TIM_BGLOADVAL: + st->regs[R_TIM_BGLOADVAL] = value; + st->regs[R_TIM_LOADVAL] = value; + break; + + case R_TIM_VAL: + case R_TIM_MIS: + break; + + default: + if (addr < R_TIM1_MAX) { + st->regs[addr] = value; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + TYPE_MSS_TIMER": 64-bit mode not supported\n"); + return; + } + break; + } + timer_update_irq(st); +} + +static const MemoryRegionOps timer_ops = { + .read = timer_read, + .write = timer_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4 + } +}; + +static void timer_hit(void *opaque) +{ + struct Msf2Timer *st = opaque; + + st->regs[R_TIM_RIS] |= TIMER_RIS_ACK; + + if (!(st->regs[R_TIM_CTRL] & TIMER_CTRL_ONESHOT)) { + timer_update(st); + } + timer_update_irq(st); +} + +static void mss_timer_init(Object *obj) +{ + MSSTimerState *t = MSS_TIMER(obj); + int i; + + /* Init all the ptimers. */ + for (i = 0; i < NUM_TIMERS; i++) { + struct Msf2Timer *st = &t->timers[i]; + + st->bh = qemu_bh_new(timer_hit, st); + st->ptimer = ptimer_init(st->bh, PTIMER_POLICY_DEFAULT); + ptimer_set_freq(st->ptimer, t->freq_hz); + sysbus_init_irq(SYS_BUS_DEVICE(obj), &st->irq); + } + + memory_region_init_io(&t->mmio, OBJECT(t), &timer_ops, t, TYPE_MSS_TIMER, + NUM_TIMERS * R_TIM1_MAX * 4); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &t->mmio); +} + +static const VMStateDescription vmstate_timers = { + .name = "mss-timer-block", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_PTIMER(ptimer, struct Msf2Timer), + VMSTATE_UINT32_ARRAY(regs, struct Msf2Timer, R_TIM1_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_mss_timer = { + .name = TYPE_MSS_TIMER, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(freq_hz, MSSTimerState), + VMSTATE_STRUCT_ARRAY(timers, MSSTimerState, NUM_TIMERS, 0, + vmstate_timers, struct Msf2Timer), + VMSTATE_END_OF_LIST() + } +}; + +static Property mss_timer_properties[] = { + /* Libero GUI shows 100Mhz as default for clocks */ + DEFINE_PROP_UINT32("clock-frequency", MSSTimerState, freq_hz, + 100 * 1000000), + DEFINE_PROP_END_OF_LIST(), +}; + +static void mss_timer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->props = mss_timer_properties; + dc->vmsd = &vmstate_mss_timer; +} + +static const TypeInfo mss_timer_info = { + .name = TYPE_MSS_TIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MSSTimerState), + .instance_init = mss_timer_init, + .class_init = mss_timer_class_init, +}; + +static void mss_timer_register_types(void) +{ + type_register_static(&mss_timer_info); +} + +type_init(mss_timer_register_types) diff --git a/hw/timer/omap_gptimer.c b/hw/timer/omap_gptimer.c index 5e3e8a6d70..6d7c8a396f 100644 --- a/hw/timer/omap_gptimer.c +++ b/hw/timer/omap_gptimer.c @@ -450,19 +450,44 @@ static void omap_gp_timer_writeh(void *opaque, hwaddr addr, s->writeh = (uint16_t) value; } +static uint64_t omap_gp_timer_readfn(void *opaque, hwaddr addr, + unsigned size) +{ + switch (size) { + case 1: + return omap_badwidth_read32(opaque, addr); + case 2: + return omap_gp_timer_readh(opaque, addr); + case 4: + return omap_gp_timer_readw(opaque, addr); + default: + g_assert_not_reached(); + } +} + +static void omap_gp_timer_writefn(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + switch (size) { + case 1: + omap_badwidth_write32(opaque, addr, value); + break; + case 2: + omap_gp_timer_writeh(opaque, addr, value); + break; + case 4: + omap_gp_timer_write(opaque, addr, value); + break; + default: + g_assert_not_reached(); + } +} + static const MemoryRegionOps omap_gp_timer_ops = { - .old_mmio = { - .read = { - omap_badwidth_read32, - omap_gp_timer_readh, - omap_gp_timer_readw, - }, - .write = { - omap_badwidth_write32, - omap_gp_timer_writeh, - omap_gp_timer_write, - }, - }, + .read = omap_gp_timer_readfn, + .write = omap_gp_timer_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, .endianness = DEVICE_NATIVE_ENDIAN, }; diff --git a/hw/timer/omap_synctimer.c b/hw/timer/omap_synctimer.c index 9ee6519793..0d75a90f3a 100644 --- a/hw/timer/omap_synctimer.c +++ b/hw/timer/omap_synctimer.c @@ -68,25 +68,32 @@ static uint32_t omap_synctimer_readh(void *opaque, hwaddr addr) } } -static void omap_synctimer_write(void *opaque, hwaddr addr, - uint32_t value) +static uint64_t omap_synctimer_readfn(void *opaque, hwaddr addr, + unsigned size) +{ + switch (size) { + case 1: + return omap_badwidth_read32(opaque, addr); + case 2: + return omap_synctimer_readh(opaque, addr); + case 4: + return omap_synctimer_readw(opaque, addr); + default: + g_assert_not_reached(); + } +} + +static void omap_synctimer_writefn(void *opaque, hwaddr addr, + uint64_t value, unsigned size) { OMAP_BAD_REG(addr); } static const MemoryRegionOps omap_synctimer_ops = { - .old_mmio = { - .read = { - omap_badwidth_read32, - omap_synctimer_readh, - omap_synctimer_readw, - }, - .write = { - omap_badwidth_write32, - omap_synctimer_write, - omap_synctimer_write, - }, - }, + .read = omap_synctimer_readfn, + .write = omap_synctimer_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, .endianness = DEVICE_NATIVE_ENDIAN, }; diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs index 757e365562..9255234c63 100644 --- a/hw/usb/Makefile.objs +++ b/hw/usb/Makefile.objs @@ -26,8 +26,10 @@ common-obj-$(CONFIG_USB_BLUETOOTH) += dev-bluetooth.o ifeq ($(CONFIG_USB_SMARTCARD),y) common-obj-y += dev-smartcard-reader.o -common-obj-$(CONFIG_SMARTCARD) += ccid-card-passthru.o -common-obj-$(CONFIG_SMARTCARD) += ccid-card-emulated.o +common-obj-$(CONFIG_SMARTCARD) += smartcard.mo +smartcard.mo-objs := ccid-card-passthru.o ccid-card-emulated.o +smartcard.mo-cflags := $(SMARTCARD_CFLAGS) +smartcard.mo-libs := $(SMARTCARD_LIBS) endif ifeq ($(CONFIG_POSIX),y) @@ -36,6 +38,8 @@ endif # usb redirection common-obj-$(CONFIG_USB_REDIR) += redirect.o quirks.o +redirect.o-cflags = $(USB_REDIR_CFLAGS) +redirect.o-libs = $(USB_REDIR_LIBS) # usb pass-through ifeq ($(CONFIG_LIBUSB)$(CONFIG_USB),yy) @@ -44,6 +48,11 @@ else common-obj-y += host-stub.o endif +host-libusb.o-cflags := $(LIBUSB_CFLAGS) +host-libusb.o-libs := $(LIBUSB_LIBS) + ifeq ($(CONFIG_USB_LIBUSB),y) common-obj-$(CONFIG_XEN) += xen-usb.o +xen-usb.o-cflags := $(LIBUSB_CFLAGS) +xen-usb.o-libs := $(LIBUSB_LIBS) endif diff --git a/hw/xen/xen_pt.h b/hw/xen/xen_pt.h index 191d9caea1..aa39a9aa5f 100644 --- a/hw/xen/xen_pt.h +++ b/hw/xen/xen_pt.h @@ -180,6 +180,7 @@ typedef struct XenPTMSI { uint32_t addr_hi; /* guest message upper address */ uint16_t data; /* guest message data */ uint32_t ctrl_offset; /* saved control offset */ + uint32_t mask; /* guest mask bits */ int pirq; /* guest pirq corresponding */ bool initialized; /* when guest MSI is initialized */ bool mapped; /* when pirq is mapped */ diff --git a/hw/xen/xen_pt_config_init.c b/hw/xen/xen_pt_config_init.c index 1f04ec5eec..a3ce33e78b 100644 --- a/hw/xen/xen_pt_config_init.c +++ b/hw/xen/xen_pt_config_init.c @@ -1315,6 +1315,22 @@ static int xen_pt_msgdata_reg_write(XenPCIPassthroughState *s, return 0; } +static int xen_pt_mask_reg_write(XenPCIPassthroughState *s, XenPTReg *cfg_entry, + uint32_t *val, uint32_t dev_value, + uint32_t valid_mask) +{ + int rc; + + rc = xen_pt_long_reg_write(s, cfg_entry, val, dev_value, valid_mask); + if (rc) { + return rc; + } + + s->msi->mask = *val; + + return 0; +} + /* MSI Capability Structure reg static information table */ static XenPTRegInfo xen_pt_emu_reg_msi[] = { /* Next Pointer reg */ @@ -1393,7 +1409,7 @@ static XenPTRegInfo xen_pt_emu_reg_msi[] = { .emu_mask = 0xFFFFFFFF, .init = xen_pt_mask_reg_init, .u.dw.read = xen_pt_long_reg_read, - .u.dw.write = xen_pt_long_reg_write, + .u.dw.write = xen_pt_mask_reg_write, }, /* Mask reg (if PCI_MSI_FLAGS_MASKBIT set, for 64-bit devices) */ { @@ -1404,7 +1420,7 @@ static XenPTRegInfo xen_pt_emu_reg_msi[] = { .emu_mask = 0xFFFFFFFF, .init = xen_pt_mask_reg_init, .u.dw.read = xen_pt_long_reg_read, - .u.dw.write = xen_pt_long_reg_write, + .u.dw.write = xen_pt_mask_reg_write, }, /* Pending reg (if PCI_MSI_FLAGS_MASKBIT set, for 32-bit devices) */ { diff --git a/hw/xen/xen_pt_msi.c b/hw/xen/xen_pt_msi.c index ff9a79f5d2..6d1e3bdeb4 100644 --- a/hw/xen/xen_pt_msi.c +++ b/hw/xen/xen_pt_msi.c @@ -24,6 +24,7 @@ #define XEN_PT_GFLAGS_SHIFT_DM 9 #define XEN_PT_GFLAGSSHIFT_DELIV_MODE 12 #define XEN_PT_GFLAGSSHIFT_TRG_MODE 15 +#define XEN_PT_GFLAGSSHIFT_UNMASKED 16 #define latch(fld) latch[PCI_MSIX_ENTRY_##fld / sizeof(uint32_t)] @@ -155,7 +156,8 @@ static int msi_msix_update(XenPCIPassthroughState *s, int pirq, bool is_msix, int msix_entry, - int *old_pirq) + int *old_pirq, + bool masked) { PCIDevice *d = &s->dev; uint8_t gvec = msi_vector(data); @@ -171,6 +173,8 @@ static int msi_msix_update(XenPCIPassthroughState *s, table_addr = s->msix->mmio_base_addr; } + gflags |= masked ? 0 : (1u << XEN_PT_GFLAGSSHIFT_UNMASKED); + rc = xc_domain_update_msi_irq(xen_xc, xen_domid, gvec, pirq, gflags, table_addr); @@ -273,8 +277,10 @@ int xen_pt_msi_setup(XenPCIPassthroughState *s) int xen_pt_msi_update(XenPCIPassthroughState *s) { XenPTMSI *msi = s->msi; + + /* Current MSI emulation in QEMU only supports 1 vector */ return msi_msix_update(s, msi_addr64(msi), msi->data, msi->pirq, - false, 0, &msi->pirq); + false, 0, &msi->pirq, msi->mask & 1); } void xen_pt_msi_disable(XenPCIPassthroughState *s) @@ -355,7 +361,8 @@ static int xen_pt_msix_update_one(XenPCIPassthroughState *s, int entry_nr, } rc = msi_msix_update(s, entry->addr, entry->data, pirq, true, - entry_nr, &entry->pirq); + entry_nr, &entry->pirq, + vec_ctrl & PCI_MSIX_ENTRY_CTRL_MASKBIT); if (!rc) { entry->updated = false; |