diff options
47 files changed, 1945 insertions, 571 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index a7f0acf866..6a197bd358 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2373,11 +2373,18 @@ S: Maintained F: include/sysemu/cryptodev*.h F: backends/cryptodev*.c +Python library +M: John Snow <jsnow@redhat.com> +M: Cleber Rosa <crosa@redhat.com> +R: Eduardo Habkost <ehabkost@redhat.com> +S: Maintained +F: python/ +T: git https://gitlab.com/jsnow/qemu.git python + Python scripts M: Eduardo Habkost <ehabkost@redhat.com> M: Cleber Rosa <crosa@redhat.com> S: Odd fixes -F: python/qemu/*py F: scripts/*.py F: tests/*.py diff --git a/accel/stubs/xen-stub.c b/accel/stubs/xen-stub.c index 7ba0b697f4..7054965c48 100644 --- a/accel/stubs/xen-stub.c +++ b/accel/stubs/xen-stub.c @@ -7,7 +7,7 @@ #include "qemu/osdep.h" #include "sysemu/xen.h" -#include "qapi/qapi-commands-misc.h" +#include "qapi/qapi-commands-migration.h" bool xen_allowed; diff --git a/accel/tcg/cputlb.c b/accel/tcg/cputlb.c index 2bbbb3ab29..42ab79c1a5 100644 --- a/accel/tcg/cputlb.c +++ b/accel/tcg/cputlb.c @@ -409,12 +409,21 @@ void tlb_flush_all_cpus_synced(CPUState *src_cpu) tlb_flush_by_mmuidx_all_cpus_synced(src_cpu, ALL_MMUIDX_BITS); } +static bool tlb_hit_page_mask_anyprot(CPUTLBEntry *tlb_entry, + target_ulong page, target_ulong mask) +{ + page &= mask; + mask &= TARGET_PAGE_MASK | TLB_INVALID_MASK; + + return (page == (tlb_entry->addr_read & mask) || + page == (tlb_addr_write(tlb_entry) & mask) || + page == (tlb_entry->addr_code & mask)); +} + static inline bool tlb_hit_page_anyprot(CPUTLBEntry *tlb_entry, target_ulong page) { - return tlb_hit_page(tlb_entry->addr_read, page) || - tlb_hit_page(tlb_addr_write(tlb_entry), page) || - tlb_hit_page(tlb_entry->addr_code, page); + return tlb_hit_page_mask_anyprot(tlb_entry, page, -1); } /** @@ -427,31 +436,45 @@ static inline bool tlb_entry_is_empty(const CPUTLBEntry *te) } /* Called with tlb_c.lock held */ -static inline bool tlb_flush_entry_locked(CPUTLBEntry *tlb_entry, - target_ulong page) +static bool tlb_flush_entry_mask_locked(CPUTLBEntry *tlb_entry, + target_ulong page, + target_ulong mask) { - if (tlb_hit_page_anyprot(tlb_entry, page)) { + if (tlb_hit_page_mask_anyprot(tlb_entry, page, mask)) { memset(tlb_entry, -1, sizeof(*tlb_entry)); return true; } return false; } +static inline bool tlb_flush_entry_locked(CPUTLBEntry *tlb_entry, + target_ulong page) +{ + return tlb_flush_entry_mask_locked(tlb_entry, page, -1); +} + /* Called with tlb_c.lock held */ -static inline void tlb_flush_vtlb_page_locked(CPUArchState *env, int mmu_idx, - target_ulong page) +static void tlb_flush_vtlb_page_mask_locked(CPUArchState *env, int mmu_idx, + target_ulong page, + target_ulong mask) { CPUTLBDesc *d = &env_tlb(env)->d[mmu_idx]; int k; assert_cpu_is_self(env_cpu(env)); for (k = 0; k < CPU_VTLB_SIZE; k++) { - if (tlb_flush_entry_locked(&d->vtable[k], page)) { + if (tlb_flush_entry_mask_locked(&d->vtable[k], page, mask)) { tlb_n_used_entries_dec(env, mmu_idx); } } } +static inline void tlb_flush_vtlb_page_locked(CPUArchState *env, int mmu_idx, + target_ulong page) +{ + tlb_flush_vtlb_page_mask_locked(env, mmu_idx, page, -1); +} + static void tlb_flush_page_locked(CPUArchState *env, int midx, target_ulong page) { @@ -666,6 +689,240 @@ void tlb_flush_page_all_cpus_synced(CPUState *src, target_ulong addr) tlb_flush_page_by_mmuidx_all_cpus_synced(src, addr, ALL_MMUIDX_BITS); } +static void tlb_flush_page_bits_locked(CPUArchState *env, int midx, + target_ulong page, unsigned bits) +{ + CPUTLBDesc *d = &env_tlb(env)->d[midx]; + CPUTLBDescFast *f = &env_tlb(env)->f[midx]; + target_ulong mask = MAKE_64BIT_MASK(0, bits); + + /* + * If @bits is smaller than the tlb size, there may be multiple entries + * within the TLB; otherwise all addresses that match under @mask hit + * the same TLB entry. + * + * TODO: Perhaps allow bits to be a few bits less than the size. + * For now, just flush the entire TLB. + */ + if (mask < f->mask) { + tlb_debug("forcing full flush midx %d (" + TARGET_FMT_lx "/" TARGET_FMT_lx ")\n", + midx, page, mask); + tlb_flush_one_mmuidx_locked(env, midx, get_clock_realtime()); + return; + } + + /* Check if we need to flush due to large pages. */ + if ((page & d->large_page_mask) == d->large_page_addr) { + tlb_debug("forcing full flush midx %d (" + TARGET_FMT_lx "/" TARGET_FMT_lx ")\n", + midx, d->large_page_addr, d->large_page_mask); + tlb_flush_one_mmuidx_locked(env, midx, get_clock_realtime()); + return; + } + + if (tlb_flush_entry_mask_locked(tlb_entry(env, midx, page), page, mask)) { + tlb_n_used_entries_dec(env, midx); + } + tlb_flush_vtlb_page_mask_locked(env, midx, page, mask); +} + +typedef struct { + target_ulong addr; + uint16_t idxmap; + uint16_t bits; +} TLBFlushPageBitsByMMUIdxData; + +static void +tlb_flush_page_bits_by_mmuidx_async_0(CPUState *cpu, + TLBFlushPageBitsByMMUIdxData d) +{ + CPUArchState *env = cpu->env_ptr; + int mmu_idx; + + assert_cpu_is_self(cpu); + + tlb_debug("page addr:" TARGET_FMT_lx "/%u mmu_map:0x%x\n", + d.addr, d.bits, d.idxmap); + + qemu_spin_lock(&env_tlb(env)->c.lock); + for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) { + if ((d.idxmap >> mmu_idx) & 1) { + tlb_flush_page_bits_locked(env, mmu_idx, d.addr, d.bits); + } + } + qemu_spin_unlock(&env_tlb(env)->c.lock); + + tb_flush_jmp_cache(cpu, d.addr); +} + +static bool encode_pbm_to_runon(run_on_cpu_data *out, + TLBFlushPageBitsByMMUIdxData d) +{ + /* We need 6 bits to hold to hold @bits up to 63. */ + if (d.idxmap <= MAKE_64BIT_MASK(0, TARGET_PAGE_BITS - 6)) { + *out = RUN_ON_CPU_TARGET_PTR(d.addr | (d.idxmap << 6) | d.bits); + return true; + } + return false; +} + +static TLBFlushPageBitsByMMUIdxData +decode_runon_to_pbm(run_on_cpu_data data) +{ + target_ulong addr_map_bits = (target_ulong) data.target_ptr; + return (TLBFlushPageBitsByMMUIdxData){ + .addr = addr_map_bits & TARGET_PAGE_MASK, + .idxmap = (addr_map_bits & ~TARGET_PAGE_MASK) >> 6, + .bits = addr_map_bits & 0x3f + }; +} + +static void tlb_flush_page_bits_by_mmuidx_async_1(CPUState *cpu, + run_on_cpu_data runon) +{ + tlb_flush_page_bits_by_mmuidx_async_0(cpu, decode_runon_to_pbm(runon)); +} + +static void tlb_flush_page_bits_by_mmuidx_async_2(CPUState *cpu, + run_on_cpu_data data) +{ + TLBFlushPageBitsByMMUIdxData *d = data.host_ptr; + tlb_flush_page_bits_by_mmuidx_async_0(cpu, *d); + g_free(d); +} + +void tlb_flush_page_bits_by_mmuidx(CPUState *cpu, target_ulong addr, + uint16_t idxmap, unsigned bits) +{ + TLBFlushPageBitsByMMUIdxData d; + run_on_cpu_data runon; + + /* If all bits are significant, this devolves to tlb_flush_page. */ + if (bits >= TARGET_LONG_BITS) { + tlb_flush_page_by_mmuidx(cpu, addr, idxmap); + return; + } + /* If no page bits are significant, this devolves to tlb_flush. */ + if (bits < TARGET_PAGE_BITS) { + tlb_flush_by_mmuidx(cpu, idxmap); + return; + } + + /* This should already be page aligned */ + d.addr = addr & TARGET_PAGE_MASK; + d.idxmap = idxmap; + d.bits = bits; + + if (qemu_cpu_is_self(cpu)) { + tlb_flush_page_bits_by_mmuidx_async_0(cpu, d); + } else if (encode_pbm_to_runon(&runon, d)) { + async_run_on_cpu(cpu, tlb_flush_page_bits_by_mmuidx_async_1, runon); + } else { + TLBFlushPageBitsByMMUIdxData *p + = g_new(TLBFlushPageBitsByMMUIdxData, 1); + + /* Otherwise allocate a structure, freed by the worker. */ + *p = d; + async_run_on_cpu(cpu, tlb_flush_page_bits_by_mmuidx_async_2, + RUN_ON_CPU_HOST_PTR(p)); + } +} + +void tlb_flush_page_bits_by_mmuidx_all_cpus(CPUState *src_cpu, + target_ulong addr, + uint16_t idxmap, + unsigned bits) +{ + TLBFlushPageBitsByMMUIdxData d; + run_on_cpu_data runon; + + /* If all bits are significant, this devolves to tlb_flush_page. */ + if (bits >= TARGET_LONG_BITS) { + tlb_flush_page_by_mmuidx_all_cpus(src_cpu, addr, idxmap); + return; + } + /* If no page bits are significant, this devolves to tlb_flush. */ + if (bits < TARGET_PAGE_BITS) { + tlb_flush_by_mmuidx_all_cpus(src_cpu, idxmap); + return; + } + + /* This should already be page aligned */ + d.addr = addr & TARGET_PAGE_MASK; + d.idxmap = idxmap; + d.bits = bits; + + if (encode_pbm_to_runon(&runon, d)) { + flush_all_helper(src_cpu, tlb_flush_page_bits_by_mmuidx_async_1, runon); + } else { + CPUState *dst_cpu; + TLBFlushPageBitsByMMUIdxData *p; + + /* Allocate a separate data block for each destination cpu. */ + CPU_FOREACH(dst_cpu) { + if (dst_cpu != src_cpu) { + p = g_new(TLBFlushPageBitsByMMUIdxData, 1); + *p = d; + async_run_on_cpu(dst_cpu, + tlb_flush_page_bits_by_mmuidx_async_2, + RUN_ON_CPU_HOST_PTR(p)); + } + } + } + + tlb_flush_page_bits_by_mmuidx_async_0(src_cpu, d); +} + +void tlb_flush_page_bits_by_mmuidx_all_cpus_synced(CPUState *src_cpu, + target_ulong addr, + uint16_t idxmap, + unsigned bits) +{ + TLBFlushPageBitsByMMUIdxData d; + run_on_cpu_data runon; + + /* If all bits are significant, this devolves to tlb_flush_page. */ + if (bits >= TARGET_LONG_BITS) { + tlb_flush_page_by_mmuidx_all_cpus_synced(src_cpu, addr, idxmap); + return; + } + /* If no page bits are significant, this devolves to tlb_flush. */ + if (bits < TARGET_PAGE_BITS) { + tlb_flush_by_mmuidx_all_cpus_synced(src_cpu, idxmap); + return; + } + + /* This should already be page aligned */ + d.addr = addr & TARGET_PAGE_MASK; + d.idxmap = idxmap; + d.bits = bits; + + if (encode_pbm_to_runon(&runon, d)) { + flush_all_helper(src_cpu, tlb_flush_page_bits_by_mmuidx_async_1, runon); + async_safe_run_on_cpu(src_cpu, tlb_flush_page_bits_by_mmuidx_async_1, + runon); + } else { + CPUState *dst_cpu; + TLBFlushPageBitsByMMUIdxData *p; + + /* Allocate a separate data block for each destination cpu. */ + CPU_FOREACH(dst_cpu) { + if (dst_cpu != src_cpu) { + p = g_new(TLBFlushPageBitsByMMUIdxData, 1); + *p = d; + async_run_on_cpu(dst_cpu, tlb_flush_page_bits_by_mmuidx_async_2, + RUN_ON_CPU_HOST_PTR(p)); + } + } + + p = g_new(TLBFlushPageBitsByMMUIdxData, 1); + *p = d; + async_safe_run_on_cpu(src_cpu, tlb_flush_page_bits_by_mmuidx_async_2, + RUN_ON_CPU_HOST_PTR(p)); + } +} + /* update the TLBs so that writes to code in the virtual page 'addr' can be detected */ void tlb_protect_code(ram_addr_t ram_addr) diff --git a/default-configs/devices/arm-softmmu.mak b/default-configs/devices/arm-softmmu.mak index 9a94ebd0be..08a32123b4 100644 --- a/default-configs/devices/arm-softmmu.mak +++ b/default-configs/devices/arm-softmmu.mak @@ -43,4 +43,3 @@ CONFIG_FSL_IMX7=y CONFIG_FSL_IMX6UL=y CONFIG_SEMIHOSTING=y CONFIG_ALLWINNER_H3=y -CONFIG_ACPI_APEI=y diff --git a/docs/devel/loads-stores.rst b/docs/devel/loads-stores.rst index 9a944ef1af..59c1225391 100644 --- a/docs/devel/loads-stores.rst +++ b/docs/devel/loads-stores.rst @@ -93,7 +93,13 @@ guest CPU state in case of a guest CPU exception. This is passed to ``cpu_restore_state()``. Therefore the value should either be 0, to indicate that the guest CPU state is already synchronized, or the result of ``GETPC()`` from the top level ``HELPER(foo)`` -function, which is a return address into the generated code. +function, which is a return address into the generated code [#gpc]_. + +.. [#gpc] Note that ``GETPC()`` should be used with great care: calling + it in other functions that are *not* the top level + ``HELPER(foo)`` will cause unexpected behavior. Instead, the + value of ``GETPC()`` should be read from the helper and passed + if needed to the functions that the helper calls. Function names follow the pattern: diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index f303c6bead..7d040827af 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -26,6 +26,7 @@ config ARM_VIRT select ACPI_MEMORY_HOTPLUG select ACPI_HW_REDUCED select ACPI_NVDIMM + select ACPI_APEI config CHEETAH bool diff --git a/hw/arm/bcm2835_peripherals.c b/hw/arm/bcm2835_peripherals.c index 15c5c72e46..48909a43c3 100644 --- a/hw/arm/bcm2835_peripherals.c +++ b/hw/arm/bcm2835_peripherals.c @@ -171,8 +171,17 @@ static void bcm2835_peripherals_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(&s->peri_mr, ST_OFFSET, sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->systmr), 0)); sysbus_connect_irq(SYS_BUS_DEVICE(&s->systmr), 0, - qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_ARM_IRQ, - INTERRUPT_ARM_TIMER)); + qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_GPU_IRQ, + INTERRUPT_TIMER0)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->systmr), 1, + qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_GPU_IRQ, + INTERRUPT_TIMER1)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->systmr), 2, + qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_GPU_IRQ, + INTERRUPT_TIMER2)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->systmr), 3, + qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_GPU_IRQ, + INTERRUPT_TIMER3)); /* UART0 */ qdev_prop_set_chr(DEVICE(&s->uart0), "chardev", serial_hd(0)); diff --git a/hw/arm/nseries.c b/hw/arm/nseries.c index e48092ca04..76fd7fe985 100644 --- a/hw/arm/nseries.c +++ b/hw/arm/nseries.c @@ -1318,6 +1318,7 @@ static void n8x0_init(MachineState *machine, g_free(sz); exit(EXIT_FAILURE); } + binfo->ram_size = machine->ram_size; memory_region_add_subregion(get_system_memory(), OMAP2_Q2_BASE, machine->ram); diff --git a/hw/arm/strongarm.c b/hw/arm/strongarm.c index d7133eea6f..ca7c385f31 100644 --- a/hw/arm/strongarm.c +++ b/hw/arm/strongarm.c @@ -935,7 +935,7 @@ struct StrongARMUARTState { uint8_t rx_start; uint8_t rx_len; - uint64_t char_transmit_time; /* time to transmit a char in ticks*/ + uint64_t char_transmit_time; /* time to transmit a char in nanoseconds */ bool wait_break_end; QEMUTimer *rx_timeout_timer; QEMUTimer *tx_timer; diff --git a/hw/i2c/microbit_i2c.c b/hw/i2c/microbit_i2c.c index 8024739820..e92f9f84ea 100644 --- a/hw/i2c/microbit_i2c.c +++ b/hw/i2c/microbit_i2c.c @@ -83,6 +83,7 @@ static const VMStateDescription microbit_i2c_vmstate = { .fields = (VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, MicrobitI2CState, MICROBIT_I2C_NREGS), VMSTATE_UINT32(read_idx, MicrobitI2CState), + VMSTATE_END_OF_LIST() }, }; diff --git a/hw/i386/xen/xen-hvm.c b/hw/i386/xen/xen-hvm.c index f3ababf33b..9519c33c09 100644 --- a/hw/i386/xen/xen-hvm.c +++ b/hw/i386/xen/xen-hvm.c @@ -24,7 +24,7 @@ #include "hw/xen/xen-bus.h" #include "hw/xen/xen-x86.h" #include "qapi/error.h" -#include "qapi/qapi-commands-misc.h" +#include "qapi/qapi-commands-migration.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" #include "qemu/range.h" diff --git a/hw/intc/bcm2835_ic.c b/hw/intc/bcm2835_ic.c index 53ab8f5881..9000d995e8 100644 --- a/hw/intc/bcm2835_ic.c +++ b/hw/intc/bcm2835_ic.c @@ -18,6 +18,7 @@ #include "migration/vmstate.h" #include "qemu/log.h" #include "qemu/module.h" +#include "trace.h" #define GPU_IRQS 64 #define ARM_IRQS 8 @@ -51,7 +52,6 @@ static void bcm2835_ic_update(BCM2835ICState *s) set = (s->gpu_irq_level & s->gpu_irq_enable) || (s->arm_irq_level & s->arm_irq_enable); qemu_set_irq(s->irq, set); - } static void bcm2835_ic_set_gpu_irq(void *opaque, int irq, int level) @@ -59,6 +59,7 @@ static void bcm2835_ic_set_gpu_irq(void *opaque, int irq, int level) BCM2835ICState *s = opaque; assert(irq >= 0 && irq < 64); + trace_bcm2835_ic_set_gpu_irq(irq, level); s->gpu_irq_level = deposit64(s->gpu_irq_level, irq, 1, level != 0); bcm2835_ic_update(s); } @@ -68,6 +69,7 @@ static void bcm2835_ic_set_arm_irq(void *opaque, int irq, int level) BCM2835ICState *s = opaque; assert(irq >= 0 && irq < 8); + trace_bcm2835_ic_set_cpu_irq(irq, level); s->arm_irq_level = deposit32(s->arm_irq_level, irq, 1, level != 0); bcm2835_ic_update(s); } diff --git a/hw/intc/bcm2836_control.c b/hw/intc/bcm2836_control.c index 53dba0080c..2ead76ffdc 100644 --- a/hw/intc/bcm2836_control.c +++ b/hw/intc/bcm2836_control.c @@ -157,22 +157,22 @@ static void bcm2836_control_set_local_irq(void *opaque, int core, int local_irq, static void bcm2836_control_set_local_irq0(void *opaque, int core, int level) { - bcm2836_control_set_local_irq(opaque, core, 0, level); + bcm2836_control_set_local_irq(opaque, core, IRQ_CNTPSIRQ, level); } static void bcm2836_control_set_local_irq1(void *opaque, int core, int level) { - bcm2836_control_set_local_irq(opaque, core, 1, level); + bcm2836_control_set_local_irq(opaque, core, IRQ_CNTPNSIRQ, level); } static void bcm2836_control_set_local_irq2(void *opaque, int core, int level) { - bcm2836_control_set_local_irq(opaque, core, 2, level); + bcm2836_control_set_local_irq(opaque, core, IRQ_CNTHPIRQ, level); } static void bcm2836_control_set_local_irq3(void *opaque, int core, int level) { - bcm2836_control_set_local_irq(opaque, core, 3, level); + bcm2836_control_set_local_irq(opaque, core, IRQ_CNTVIRQ, level); } static void bcm2836_control_set_gpu_irq(void *opaque, int irq, int level) diff --git a/hw/intc/trace-events b/hw/intc/trace-events index 527c3f76ca..22782b3f08 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -199,3 +199,7 @@ nvic_sysreg_write(uint64_t addr, uint32_t value, unsigned size) "NVIC sysreg wri heathrow_write(uint64_t addr, unsigned int n, uint64_t value) "0x%"PRIx64" %u: 0x%"PRIx64 heathrow_read(uint64_t addr, unsigned int n, uint64_t value) "0x%"PRIx64" %u: 0x%"PRIx64 heathrow_set_irq(int num, int level) "set_irq: num=0x%02x level=%d" + +# bcm2835_ic.c +bcm2835_ic_set_gpu_irq(int irq, int level) "GPU irq #%d level %d" +bcm2835_ic_set_cpu_irq(int irq, int level) "CPU irq #%d level %d" diff --git a/hw/timer/bcm2835_systmr.c b/hw/timer/bcm2835_systmr.c index 3387a6214a..67669a57ff 100644 --- a/hw/timer/bcm2835_systmr.c +++ b/hw/timer/bcm2835_systmr.c @@ -28,20 +28,13 @@ REG32(COMPARE1, 0x10) REG32(COMPARE2, 0x14) REG32(COMPARE3, 0x18) -static void bcm2835_systmr_update_irq(BCM2835SystemTimerState *s) +static void bcm2835_systmr_timer_expire(void *opaque) { - bool enable = !!s->reg.status; + BCM2835SystemTimerCompare *tmr = opaque; - trace_bcm2835_systmr_irq(enable); - qemu_set_irq(s->irq, enable); -} - -static void bcm2835_systmr_update_compare(BCM2835SystemTimerState *s, - unsigned timer_index) -{ - /* TODO fow now, since neither Linux nor U-boot use these timers. */ - qemu_log_mask(LOG_UNIMP, "COMPARE register %u not implemented\n", - timer_index); + trace_bcm2835_systmr_timer_expired(tmr->id); + tmr->state->reg.ctrl_status |= 1 << tmr->id; + qemu_set_irq(tmr->irq, 1); } static uint64_t bcm2835_systmr_read(void *opaque, hwaddr offset, @@ -52,7 +45,7 @@ static uint64_t bcm2835_systmr_read(void *opaque, hwaddr offset, switch (offset) { case A_CTRL_STATUS: - r = s->reg.status; + r = s->reg.ctrl_status; break; case A_COMPARE0 ... A_COMPARE3: r = s->reg.compare[(offset - A_COMPARE0) >> 2]; @@ -75,19 +68,33 @@ static uint64_t bcm2835_systmr_read(void *opaque, hwaddr offset, } static void bcm2835_systmr_write(void *opaque, hwaddr offset, - uint64_t value, unsigned size) + uint64_t value64, unsigned size) { BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque); + int index; + uint32_t value = value64; + uint32_t triggers_delay_us; + uint64_t now; trace_bcm2835_systmr_write(offset, value); switch (offset) { case A_CTRL_STATUS: - s->reg.status &= ~value; /* Ack */ - bcm2835_systmr_update_irq(s); + s->reg.ctrl_status &= ~value; /* Ack */ + for (index = 0; index < ARRAY_SIZE(s->tmr); index++) { + if (extract32(value, index, 1)) { + trace_bcm2835_systmr_irq_ack(index); + qemu_set_irq(s->tmr[index].irq, 0); + } + } break; case A_COMPARE0 ... A_COMPARE3: - s->reg.compare[(offset - A_COMPARE0) >> 2] = value; - bcm2835_systmr_update_compare(s, (offset - A_COMPARE0) >> 2); + index = (offset - A_COMPARE0) >> 2; + s->reg.compare[index] = value; + now = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL); + /* Compare lower 32-bits of the free-running counter. */ + triggers_delay_us = value - now; + trace_bcm2835_systmr_run(index, triggers_delay_us); + timer_mod(&s->tmr[index].timer, now + triggers_delay_us); break; case A_COUNTER_LOW: case A_COUNTER_HIGH: @@ -125,7 +132,14 @@ static void bcm2835_systmr_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_systmr_ops, s, "bcm2835-sys-timer", 0x20); sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); - sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + + for (size_t i = 0; i < ARRAY_SIZE(s->tmr); i++) { + s->tmr[i].id = i; + s->tmr[i].state = s; + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->tmr[i].irq); + timer_init_us(&s->tmr[i].timer, QEMU_CLOCK_VIRTUAL, + bcm2835_systmr_timer_expire, &s->tmr[i]); + } } static const VMStateDescription bcm2835_systmr_vmstate = { @@ -133,8 +147,9 @@ static const VMStateDescription bcm2835_systmr_vmstate = { .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { - VMSTATE_UINT32(reg.status, BCM2835SystemTimerState), - VMSTATE_UINT32_ARRAY(reg.compare, BCM2835SystemTimerState, 4), + VMSTATE_UINT32(reg.ctrl_status, BCM2835SystemTimerState), + VMSTATE_UINT32_ARRAY(reg.compare, BCM2835SystemTimerState, + BCM2835_SYSTIMER_COUNT), VMSTATE_END_OF_LIST() } }; diff --git a/hw/timer/trace-events b/hw/timer/trace-events index b996d99200..7a4326d956 100644 --- a/hw/timer/trace-events +++ b/hw/timer/trace-events @@ -77,9 +77,11 @@ nrf51_timer_write(uint8_t timer_id, uint64_t addr, uint32_t value, unsigned size nrf51_timer_set_count(uint8_t timer_id, uint8_t counter_id, uint32_t value) "timer %u counter %u count 0x%" PRIx32 # bcm2835_systmr.c -bcm2835_systmr_irq(bool enable) "timer irq state %u" +bcm2835_systmr_timer_expired(unsigned id) "timer #%u expired" +bcm2835_systmr_irq_ack(unsigned id) "timer #%u acked" bcm2835_systmr_read(uint64_t offset, uint64_t data) "timer read: offset 0x%" PRIx64 " data 0x%" PRIx64 -bcm2835_systmr_write(uint64_t offset, uint64_t data) "timer write: offset 0x%" PRIx64 " data 0x%" PRIx64 +bcm2835_systmr_write(uint64_t offset, uint32_t data) "timer write: offset 0x%" PRIx64 " data 0x%" PRIx32 +bcm2835_systmr_run(unsigned id, uint64_t delay_us) "timer #%u expiring in %"PRIu64" us" # avr_timer16.c avr_timer16_read(uint8_t addr, uint8_t value) "timer16 read addr:%u value:%u" diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index 66f9b4cca6..4707ac140c 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -251,6 +251,25 @@ void tlb_flush_by_mmuidx_all_cpus(CPUState *cpu, uint16_t idxmap); * depend on when the guests translation ends the TB. */ void tlb_flush_by_mmuidx_all_cpus_synced(CPUState *cpu, uint16_t idxmap); + +/** + * tlb_flush_page_bits_by_mmuidx + * @cpu: CPU whose TLB should be flushed + * @addr: virtual address of page to be flushed + * @idxmap: bitmap of mmu indexes to flush + * @bits: number of significant bits in address + * + * Similar to tlb_flush_page_mask, but with a bitmap of indexes. + */ +void tlb_flush_page_bits_by_mmuidx(CPUState *cpu, target_ulong addr, + uint16_t idxmap, unsigned bits); + +/* Similarly, with broadcast and syncing. */ +void tlb_flush_page_bits_by_mmuidx_all_cpus(CPUState *cpu, target_ulong addr, + uint16_t idxmap, unsigned bits); +void tlb_flush_page_bits_by_mmuidx_all_cpus_synced + (CPUState *cpu, target_ulong addr, uint16_t idxmap, unsigned bits); + /** * tlb_set_page_with_attrs: * @cpu: CPU to add this TLB entry for @@ -337,6 +356,23 @@ static inline void tlb_flush_by_mmuidx_all_cpus_synced(CPUState *cpu, uint16_t idxmap) { } +static inline void tlb_flush_page_bits_by_mmuidx(CPUState *cpu, + target_ulong addr, + uint16_t idxmap, + unsigned bits) +{ +} +static inline void tlb_flush_page_bits_by_mmuidx_all_cpus(CPUState *cpu, + target_ulong addr, + uint16_t idxmap, + unsigned bits) +{ +} +static inline void +tlb_flush_page_bits_by_mmuidx_all_cpus_synced(CPUState *cpu, target_ulong addr, + uint16_t idxmap, unsigned bits) +{ +} #endif /** * probe_access: diff --git a/include/hw/timer/bcm2835_systmr.h b/include/hw/timer/bcm2835_systmr.h index 7ce8f6ef4d..bd3097d746 100644 --- a/include/hw/timer/bcm2835_systmr.h +++ b/include/hw/timer/bcm2835_systmr.h @@ -11,23 +11,32 @@ #include "hw/sysbus.h" #include "hw/irq.h" +#include "qemu/timer.h" #include "qom/object.h" #define TYPE_BCM2835_SYSTIMER "bcm2835-sys-timer" OBJECT_DECLARE_SIMPLE_TYPE(BCM2835SystemTimerState, BCM2835_SYSTIMER) +#define BCM2835_SYSTIMER_COUNT 4 + +typedef struct { + unsigned id; + QEMUTimer timer; + qemu_irq irq; + BCM2835SystemTimerState *state; +} BCM2835SystemTimerCompare; + struct BCM2835SystemTimerState { /*< private >*/ SysBusDevice parent_obj; /*< public >*/ MemoryRegion iomem; - qemu_irq irq; - struct { - uint32_t status; - uint32_t compare[4]; + uint32_t ctrl_status; + uint32_t compare[BCM2835_SYSTIMER_COUNT]; } reg; + BCM2835SystemTimerCompare tmr[BCM2835_SYSTIMER_COUNT]; }; #endif diff --git a/migration/savevm.c b/migration/savevm.c index d2e141f7b1..ff33e210eb 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -42,7 +42,6 @@ #include "postcopy-ram.h" #include "qapi/error.h" #include "qapi/qapi-commands-migration.h" -#include "qapi/qapi-commands-misc.h" #include "qapi/qmp/qerror.h" #include "qemu/error-report.h" #include "sysemu/cpus.h" diff --git a/python/mypy.ini b/python/mypy.ini new file mode 100644 index 0000000000..1a581c5f1e --- /dev/null +++ b/python/mypy.ini @@ -0,0 +1,4 @@ +[mypy] +strict = True +python_version = 3.6 +warn_unused_configs = True diff --git a/python/qemu/.isort.cfg b/python/qemu/.isort.cfg new file mode 100644 index 0000000000..6d0fd6cc0d --- /dev/null +++ b/python/qemu/.isort.cfg @@ -0,0 +1,7 @@ +[settings] +force_grid_wrap=4 +force_sort_within_sections=True +include_trailing_comma=True +line_length=72 +lines_after_imports=2 +multi_line_output=3 \ No newline at end of file diff --git a/python/qemu/accel.py b/python/qemu/accel.py index 7fabe62920..297933df2a 100644 --- a/python/qemu/accel.py +++ b/python/qemu/accel.py @@ -17,6 +17,8 @@ accelerators. import logging import os import subprocess +from typing import List, Optional + LOG = logging.getLogger(__name__) @@ -29,7 +31,7 @@ ADDITIONAL_ARCHES = { } -def list_accel(qemu_bin): +def list_accel(qemu_bin: str) -> List[str]: """ List accelerators enabled in the QEMU binary. @@ -49,7 +51,8 @@ def list_accel(qemu_bin): return [acc.strip() for acc in out.splitlines()[1:]] -def kvm_available(target_arch=None, qemu_bin=None): +def kvm_available(target_arch: Optional[str] = None, + qemu_bin: Optional[str] = None) -> bool: """ Check if KVM is available using the following heuristic: - Kernel module is present in the host; @@ -72,7 +75,7 @@ def kvm_available(target_arch=None, qemu_bin=None): return True -def tcg_available(qemu_bin): +def tcg_available(qemu_bin: str) -> bool: """ Check if TCG is available. diff --git a/python/qemu/console_socket.py b/python/qemu/console_socket.py index 70869fbbdc..f060d79e06 100644 --- a/python/qemu/console_socket.py +++ b/python/qemu/console_socket.py @@ -13,10 +13,11 @@ which can drain a socket and optionally dump the bytes to file. # the COPYING file in the top-level directory. # +from collections import deque import socket import threading -from collections import deque import time +from typing import Deque, Optional class ConsoleSocket(socket.socket): @@ -29,22 +30,22 @@ class ConsoleSocket(socket.socket): Optionally a file path can be passed in and we will also dump the characters to this file for debugging purposes. """ - def __init__(self, address, file=None, drain=False): - self._recv_timeout_sec = 300 + def __init__(self, address: str, file: Optional[str] = None, + drain: bool = False): + self._recv_timeout_sec = 300.0 self._sleep_time = 0.5 - self._buffer = deque() + self._buffer: Deque[int] = deque() socket.socket.__init__(self, socket.AF_UNIX, socket.SOCK_STREAM) self.connect(address) self._logfile = None if file: - self._logfile = open(file, "w") + self._logfile = open(file, "bw") self._open = True + self._drain_thread = None if drain: self._drain_thread = self._thread_start() - else: - self._drain_thread = None - def _drain_fn(self): + def _drain_fn(self) -> None: """Drains the socket and runs while the socket is open.""" while self._open: try: @@ -55,7 +56,7 @@ class ConsoleSocket(socket.socket): # self._open is set to False. time.sleep(self._sleep_time) - def _thread_start(self): + def _thread_start(self) -> threading.Thread: """Kick off a thread to drain the socket.""" # Configure socket to not block and timeout. # This allows our drain thread to not block @@ -67,7 +68,7 @@ class ConsoleSocket(socket.socket): drain_thread.start() return drain_thread - def close(self): + def close(self) -> None: """Close the base object and wait for the thread to terminate""" if self._open: self._open = False @@ -79,51 +80,42 @@ class ConsoleSocket(socket.socket): self._logfile.close() self._logfile = None - def _drain_socket(self): + def _drain_socket(self) -> None: """process arriving characters into in memory _buffer""" data = socket.socket.recv(self, 1) - # latin1 is needed since there are some chars - # we are receiving that cannot be encoded to utf-8 - # such as 0xe2, 0x80, 0xA6. - string = data.decode("latin1") if self._logfile: - self._logfile.write("{}".format(string)) + self._logfile.write(data) self._logfile.flush() - for c in string: - self._buffer.extend(c) + self._buffer.extend(data) - def recv(self, bufsize=1): + def recv(self, bufsize: int = 1, flags: int = 0) -> bytes: """Return chars from in memory buffer. Maintains the same API as socket.socket.recv. """ if self._drain_thread is None: # Not buffering the socket, pass thru to socket. - return socket.socket.recv(self, bufsize) + return socket.socket.recv(self, bufsize, flags) + assert not flags, "Cannot pass flags to recv() in drained mode" start_time = time.time() while len(self._buffer) < bufsize: time.sleep(self._sleep_time) elapsed_sec = time.time() - start_time if elapsed_sec > self._recv_timeout_sec: raise socket.timeout - chars = ''.join([self._buffer.popleft() for i in range(bufsize)]) - # We choose to use latin1 to remain consistent with - # handle_read() and give back the same data as the user would - # receive if they were reading directly from the - # socket w/o our intervention. - return chars.encode("latin1") + return bytes((self._buffer.popleft() for i in range(bufsize))) - def setblocking(self, value): + def setblocking(self, value: bool) -> None: """When not draining we pass thru to the socket, since when draining we control socket blocking. """ if self._drain_thread is None: socket.socket.setblocking(self, value) - def settimeout(self, seconds): + def settimeout(self, value: Optional[float]) -> None: """When not draining we pass thru to the socket, since when draining we control the timeout. """ - if seconds is not None: - self._recv_timeout_sec = seconds + if value is not None: + self._recv_timeout_sec = value if self._drain_thread is None: - socket.socket.settimeout(self, seconds) + socket.socket.settimeout(self, value) diff --git a/python/qemu/machine.py b/python/qemu/machine.py index 82f3731fc3..6420f01bed 100644 --- a/python/qemu/machine.py +++ b/python/qemu/machine.py @@ -18,17 +18,29 @@ which provides facilities for managing the lifetime of a QEMU VM. # import errno +from itertools import chain import logging import os -import subprocess import shutil import signal +import socket +import subprocess import tempfile -from typing import Optional, Type from types import TracebackType -from . import console_socket +from typing import ( + Any, + BinaryIO, + Dict, + List, + Optional, + Sequence, + Tuple, + Type, +) + +from . import console_socket, qmp +from .qmp import QMPMessage, QMPReturnValue, SocketAddrT -from . import qmp LOG = logging.getLogger(__name__) @@ -57,7 +69,7 @@ class AbnormalShutdown(QEMUMachineError): class QEMUMachine: """ - A QEMU VM + A QEMU VM. Use this object as a context manager to ensure the QEMU process terminates:: @@ -67,10 +79,17 @@ class QEMUMachine: # vm is guaranteed to be shut down here """ - def __init__(self, binary, args=None, wrapper=None, name=None, - test_dir="/var/tmp", monitor_address=None, - socket_scm_helper=None, sock_dir=None, - drain_console=False, console_log=None): + def __init__(self, + binary: str, + args: Sequence[str] = (), + wrapper: Sequence[str] = (), + name: Optional[str] = None, + test_dir: str = "/var/tmp", + monitor_address: Optional[SocketAddrT] = None, + socket_scm_helper: Optional[str] = None, + sock_dir: Optional[str] = None, + drain_console: bool = False, + console_log: Optional[str] = None): ''' Initialize a QEMUMachine @@ -82,45 +101,30 @@ class QEMUMachine: @param monitor_address: address for QMP monitor @param socket_scm_helper: helper program, required for send_fd_scm() @param sock_dir: where to create socket (overrides test_dir for sock) - @param console_log: (optional) path to console log file @param drain_console: (optional) True to drain console socket to buffer + @param console_log: (optional) path to console log file @note: Qemu process is not started until launch() is used. ''' - if args is None: - args = [] - if wrapper is None: - wrapper = [] - if name is None: - name = "qemu-%d" % os.getpid() - if sock_dir is None: - sock_dir = test_dir - self._name = name - self._monitor_address = monitor_address - self._vm_monitor = None - self._qemu_log_path = None - self._qemu_log_file = None - self._popen = None + # Direct user configuration + self._binary = binary - self._args = list(args) # Force copy args in case we modify them + self._args = list(args) self._wrapper = wrapper - self._events = [] - self._iolog = None - self._socket_scm_helper = socket_scm_helper - self._qmp_set = True # Enable QMP monitor by default. - self._qmp = None - self._qemu_full_args = None + + self._name = name or "qemu-%d" % os.getpid() self._test_dir = test_dir - self._temp_dir = None - self._sock_dir = sock_dir - self._launched = False - self._machine = None - self._console_index = 0 - self._console_set = False - self._console_device_type = None - self._console_address = None - self._console_socket = None - self._remove_files = [] - self._user_killed = False + self._sock_dir = sock_dir or self._test_dir + self._socket_scm_helper = socket_scm_helper + + if monitor_address is not None: + self._monitor_address = monitor_address + self._remove_monitor_sockfile = False + else: + self._monitor_address = os.path.join( + self._sock_dir, f"{self._name}-monitor.sock" + ) + self._remove_monitor_sockfile = True + self._console_log_path = console_log if self._console_log_path: # In order to log the console, buffering needs to be enabled. @@ -128,7 +132,29 @@ class QEMUMachine: else: self._drain_console = drain_console - def __enter__(self): + # Runstate + self._qemu_log_path: Optional[str] = None + self._qemu_log_file: Optional[BinaryIO] = None + self._popen: Optional['subprocess.Popen[bytes]'] = None + self._events: List[QMPMessage] = [] + self._iolog: Optional[str] = None + self._qmp_set = True # Enable QMP monitor by default. + self._qmp_connection: Optional[qmp.QEMUMonitorProtocol] = None + self._qemu_full_args: Tuple[str, ...] = () + self._temp_dir: Optional[str] = None + self._launched = False + self._machine: Optional[str] = None + self._console_index = 0 + self._console_set = False + self._console_device_type: Optional[str] = None + self._console_address = os.path.join( + self._sock_dir, f"{self._name}-console.sock" + ) + self._console_socket: Optional[socket.socket] = None + self._remove_files: List[str] = [] + self._user_killed = False + + def __enter__(self) -> 'QEMUMachine': return self def __exit__(self, @@ -137,14 +163,15 @@ class QEMUMachine: exc_tb: Optional[TracebackType]) -> None: self.shutdown() - def add_monitor_null(self): + def add_monitor_null(self) -> None: """ This can be used to add an unused monitor instance. """ self._args.append('-monitor') self._args.append('null') - def add_fd(self, fd, fdset, opaque, opts=''): + def add_fd(self, fd: int, fdset: int, + opaque: str, opts: str = '') -> 'QEMUMachine': """ Pass a file descriptor to the VM """ @@ -163,7 +190,8 @@ class QEMUMachine: self._args.append(','.join(options)) return self - def send_fd_scm(self, fd=None, file_path=None): + def send_fd_scm(self, fd: Optional[int] = None, + file_path: Optional[str] = None) -> int: """ Send an fd or file_path to socket_scm_helper. @@ -207,7 +235,7 @@ class QEMUMachine: return proc.returncode @staticmethod - def _remove_if_exists(path): + def _remove_if_exists(path: str) -> None: """ Remove file object at path if it exists """ @@ -218,46 +246,52 @@ class QEMUMachine: return raise - def is_running(self): + def is_running(self) -> bool: """Returns true if the VM is running.""" return self._popen is not None and self._popen.poll() is None - def exitcode(self): + @property + def _subp(self) -> 'subprocess.Popen[bytes]': + if self._popen is None: + raise QEMUMachineError('Subprocess pipe not present') + return self._popen + + def exitcode(self) -> Optional[int]: """Returns the exit code if possible, or None.""" if self._popen is None: return None return self._popen.poll() - def get_pid(self): + def get_pid(self) -> Optional[int]: """Returns the PID of the running process, or None.""" if not self.is_running(): return None - return self._popen.pid + return self._subp.pid - def _load_io_log(self): + def _load_io_log(self) -> None: if self._qemu_log_path is not None: with open(self._qemu_log_path, "r") as iolog: self._iolog = iolog.read() - def _base_args(self): + @property + def _base_args(self) -> List[str]: args = ['-display', 'none', '-vga', 'none'] + if self._qmp_set: if isinstance(self._monitor_address, tuple): - moncdev = "socket,id=mon,host=%s,port=%s" % ( - self._monitor_address[0], - self._monitor_address[1]) + moncdev = "socket,id=mon,host={},port={}".format( + *self._monitor_address + ) else: - moncdev = 'socket,id=mon,path=%s' % self._vm_monitor + moncdev = f"socket,id=mon,path={self._monitor_address}" args.extend(['-chardev', moncdev, '-mon', 'chardev=mon,mode=control']) + if self._machine is not None: args.extend(['-machine', self._machine]) for _ in range(self._console_index): args.extend(['-serial', 'null']) if self._console_set: - self._console_address = os.path.join(self._sock_dir, - self._name + "-console.sock") - self._remove_files.append(self._console_address) chardev = ('socket,id=console,path=%s,server,nowait' % self._console_address) args.extend(['-chardev', chardev]) @@ -268,26 +302,29 @@ class QEMUMachine: args.extend(['-device', device]) return args - def _pre_launch(self): + def _pre_launch(self) -> None: self._temp_dir = tempfile.mkdtemp(dir=self._test_dir) self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log") self._qemu_log_file = open(self._qemu_log_path, 'wb') + if self._console_set: + self._remove_files.append(self._console_address) + if self._qmp_set: - if self._monitor_address is not None: - self._vm_monitor = self._monitor_address - else: - self._vm_monitor = os.path.join(self._sock_dir, - self._name + "-monitor.sock") - self._remove_files.append(self._vm_monitor) - self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor, server=True, - nickname=self._name) - - def _post_launch(self): - if self._qmp: + if self._remove_monitor_sockfile: + assert isinstance(self._monitor_address, str) + self._remove_files.append(self._monitor_address) + self._qmp_connection = qmp.QEMUMonitorProtocol( + self._monitor_address, + server=True, + nickname=self._name + ) + + def _post_launch(self) -> None: + if self._qmp_connection: self._qmp.accept() - def _post_shutdown(self): + def _post_shutdown(self) -> None: """ Called to cleanup the VM instance after the process has exited. May also be called after a failed launch. @@ -295,9 +332,9 @@ class QEMUMachine: # Comprehensive reset for the failed launch case: self._early_cleanup() - if self._qmp: + if self._qmp_connection: self._qmp.close() - self._qmp = None + self._qmp_connection = None self._load_io_log() @@ -327,7 +364,7 @@ class QEMUMachine: self._user_killed = False self._launched = False - def launch(self): + def launch(self) -> None: """ Launch the VM and make sure we cleanup and expose the command line/output in case of exception @@ -337,7 +374,7 @@ class QEMUMachine: raise QEMUMachineError('VM already launched') self._iolog = None - self._qemu_full_args = None + self._qemu_full_args = () try: self._launch() self._launched = True @@ -351,14 +388,18 @@ class QEMUMachine: LOG.debug('Output: %r', self._iolog) raise - def _launch(self): + def _launch(self) -> None: """ Launch the VM and establish a QMP connection """ devnull = open(os.path.devnull, 'rb') self._pre_launch() - self._qemu_full_args = (self._wrapper + [self._binary] + - self._base_args() + self._args) + self._qemu_full_args = tuple( + chain(self._wrapper, + [self._binary], + self._base_args, + self._args) + ) LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args)) self._popen = subprocess.Popen(self._qemu_full_args, stdin=devnull, @@ -390,8 +431,8 @@ class QEMUMachine: waiting for the QEMU process to terminate. """ self._early_cleanup() - self._popen.kill() - self._popen.wait(timeout=60) + self._subp.kill() + self._subp.wait(timeout=60) def _soft_shutdown(self, timeout: Optional[int], has_quit: bool = False) -> None: @@ -409,13 +450,13 @@ class QEMUMachine: """ self._early_cleanup() - if self._qmp is not None: + if self._qmp_connection: if not has_quit: # Might raise ConnectionReset self._qmp.cmd('quit') # May raise subprocess.TimeoutExpired - self._popen.wait(timeout=timeout) + self._subp.wait(timeout=timeout) def _do_shutdown(self, timeout: Optional[int], has_quit: bool = False) -> None: @@ -466,7 +507,7 @@ class QEMUMachine: finally: self._post_shutdown() - def kill(self): + def kill(self) -> None: """ Terminate the VM forcefully, wait for it to exit, and perform cleanup. """ @@ -481,7 +522,7 @@ class QEMUMachine: """ self.shutdown(has_quit=True, timeout=timeout) - def set_qmp_monitor(self, enabled=True): + def set_qmp_monitor(self, enabled: bool = True) -> None: """ Set the QMP monitor. @@ -490,39 +531,45 @@ class QEMUMachine: line. Default is True. @note: call this function before launch(). """ - if enabled: - self._qmp_set = True - else: - self._qmp_set = False - self._qmp = None + self._qmp_set = enabled - def qmp(self, cmd, conv_keys=True, **args): - """ - Invoke a QMP command and return the response dict - """ + @property + def _qmp(self) -> qmp.QEMUMonitorProtocol: + if self._qmp_connection is None: + raise QEMUMachineError("Attempt to access QMP with no connection") + return self._qmp_connection + + @classmethod + def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]: qmp_args = dict() for key, value in args.items(): - if conv_keys: + if _conv_keys: qmp_args[key.replace('_', '-')] = value else: qmp_args[key] = value + return qmp_args + def qmp(self, cmd: str, + conv_keys: bool = True, + **args: Any) -> QMPMessage: + """ + Invoke a QMP command and return the response dict + """ + qmp_args = self._qmp_args(conv_keys, **args) return self._qmp.cmd(cmd, args=qmp_args) - def command(self, cmd, conv_keys=True, **args): + def command(self, cmd: str, + conv_keys: bool = True, + **args: Any) -> QMPReturnValue: """ Invoke a QMP command. On success return the response dict. On failure raise an exception. """ - reply = self.qmp(cmd, conv_keys, **args) - if reply is None: - raise qmp.QMPError("Monitor is closed") - if "error" in reply: - raise qmp.QMPResponseError(reply) - return reply["return"] + qmp_args = self._qmp_args(conv_keys, **args) + return self._qmp.command(cmd, **qmp_args) - def get_qmp_event(self, wait=False): + def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]: """ Poll for one queued QMP events and return it """ @@ -530,7 +577,7 @@ class QEMUMachine: return self._events.pop(0) return self._qmp.pull_event(wait=wait) - def get_qmp_events(self, wait=False): + def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]: """ Poll for queued QMP events and return a list of dicts """ @@ -541,7 +588,7 @@ class QEMUMachine: return events @staticmethod - def event_match(event, match=None): + def event_match(event: Any, match: Optional[Any]) -> bool: """ Check if an event matches optional match criteria. @@ -571,9 +618,11 @@ class QEMUMachine: return True except TypeError: # either match or event wasn't iterable (not a dict) - return match == event + return bool(match == event) - def event_wait(self, name, timeout=60.0, match=None): + def event_wait(self, name: str, + timeout: float = 60.0, + match: Optional[QMPMessage] = None) -> Optional[QMPMessage]: """ event_wait waits for and returns a named event from QMP with a timeout. @@ -583,22 +632,33 @@ class QEMUMachine: """ return self.events_wait([(name, match)], timeout) - def events_wait(self, events, timeout=60.0): + def events_wait(self, + events: Sequence[Tuple[str, Any]], + timeout: float = 60.0) -> Optional[QMPMessage]: """ - events_wait waits for and returns a named event - from QMP with a timeout. + events_wait waits for and returns a single named event from QMP. + In the case of multiple qualifying events, this function returns the + first one. - events: a sequence of (name, match_criteria) tuples. - The match criteria are optional and may be None. - See event_match for details. - timeout: QEMUMonitorProtocol.pull_event timeout parameter. + :param events: A sequence of (name, match_criteria) tuples. + The match criteria are optional and may be None. + See event_match for details. + :param timeout: Optional timeout, in seconds. + See QEMUMonitorProtocol.pull_event. + + :raise QMPTimeoutError: If timeout was non-zero and no matching events + were found. + :return: A QMP event matching the filter criteria. + If timeout was 0 and no event matched, None. """ - def _match(event): + def _match(event: QMPMessage) -> bool: for name, match in events: if event['event'] == name and self.event_match(event, match): return True return False + event: Optional[QMPMessage] + # Search cached events for event in self._events: if _match(event): @@ -608,26 +668,30 @@ class QEMUMachine: # Poll for new events while True: event = self._qmp.pull_event(wait=timeout) + if event is None: + # NB: None is only returned when timeout is false-ish. + # Timeouts raise QMPTimeoutError instead! + break if _match(event): return event self._events.append(event) return None - def get_log(self): + def get_log(self) -> Optional[str]: """ After self.shutdown or failed qemu execution, this returns the output of the qemu process. """ return self._iolog - def add_args(self, *args): + def add_args(self, *args: str) -> None: """ Adds to the list of extra arguments to be given to the QEMU binary """ self._args.extend(args) - def set_machine(self, machine_type): + def set_machine(self, machine_type: str) -> None: """ Sets the machine type @@ -636,7 +700,9 @@ class QEMUMachine: """ self._machine = machine_type - def set_console(self, device_type=None, console_index=0): + def set_console(self, + device_type: Optional[str] = None, + console_index: int = 0) -> None: """ Sets the device type for a console device @@ -667,7 +733,7 @@ class QEMUMachine: self._console_index = console_index @property - def console_socket(self): + def console_socket(self) -> socket.socket: """ Returns a socket connected to the console """ diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py index 7935dababb..2cd4d43036 100644 --- a/python/qemu/qmp.py +++ b/python/qemu/qmp.py @@ -7,21 +7,22 @@ # This work is licensed under the terms of the GNU GPL, version 2. See # the COPYING file in the top-level directory. -import json import errno -import socket +import json import logging +import socket +from types import TracebackType from typing import ( Any, - cast, Dict, + List, Optional, TextIO, - Type, Tuple, + Type, Union, + cast, ) -from types import TracebackType # QMPMessage is a QMP Message of any kind. @@ -90,7 +91,9 @@ class QEMUMonitorProtocol: #: Logger object for debugging messages logger = logging.getLogger('QMP') - def __init__(self, address, server=False, nickname=None): + def __init__(self, address: SocketAddrT, + server: bool = False, + nickname: Optional[str] = None): """ Create a QEMUMonitorProtocol class. @@ -102,7 +105,7 @@ class QEMUMonitorProtocol: @note No connection is established, this is done by the connect() or accept() methods """ - self.__events = [] + self.__events: List[QMPMessage] = [] self.__address = address self.__sock = self.__get_sock() self.__sockfile: Optional[TextIO] = None @@ -114,14 +117,14 @@ class QEMUMonitorProtocol: self.__sock.bind(self.__address) self.__sock.listen(1) - def __get_sock(self): + def __get_sock(self) -> socket.socket: if isinstance(self.__address, tuple): family = socket.AF_INET else: family = socket.AF_UNIX return socket.socket(family, socket.SOCK_STREAM) - def __negotiate_capabilities(self): + def __negotiate_capabilities(self) -> QMPMessage: greeting = self.__json_read() if greeting is None or "QMP" not in greeting: raise QMPConnectError @@ -131,7 +134,7 @@ class QEMUMonitorProtocol: return greeting raise QMPCapabilitiesError - def __json_read(self, only_event=False): + def __json_read(self, only_event: bool = False) -> Optional[QMPMessage]: assert self.__sockfile is not None while True: data = self.__sockfile.readline() @@ -148,7 +151,7 @@ class QEMUMonitorProtocol: continue return resp - def __get_events(self, wait=False): + def __get_events(self, wait: Union[bool, float] = False) -> None: """ Check for new events in the stream and cache them in __events. @@ -161,15 +164,19 @@ class QEMUMonitorProtocol: retrieved or if some other error occurred. """ + # Current timeout and blocking status + current_timeout = self.__sock.gettimeout() + # Check for new events regardless and pull them into the cache: - self.__sock.setblocking(False) + self.__sock.settimeout(0) # i.e. setblocking(False) try: self.__json_read() except OSError as err: - if err.errno == errno.EAGAIN: - # No data available - pass - self.__sock.setblocking(True) + # EAGAIN: No data available; not critical + if err.errno != errno.EAGAIN: + raise + finally: + self.__sock.settimeout(current_timeout) # Wait for new events, if needed. # if wait is 0.0, this means "no wait" and is also implicitly false. @@ -178,15 +185,18 @@ class QEMUMonitorProtocol: self.__sock.settimeout(wait) try: ret = self.__json_read(only_event=True) - except socket.timeout: - raise QMPTimeoutError("Timeout waiting for event") - except: - raise QMPConnectError("Error while reading from socket") + except socket.timeout as err: + raise QMPTimeoutError("Timeout waiting for event") from err + except Exception as err: + msg = "Error while reading from socket" + raise QMPConnectError(msg) from err + finally: + self.__sock.settimeout(current_timeout) + if ret is None: raise QMPConnectError("Error while reading from socket") - self.__sock.settimeout(None) - def __enter__(self): + def __enter__(self) -> 'QEMUMonitorProtocol': # Implement context manager enter function. return self @@ -199,7 +209,7 @@ class QEMUMonitorProtocol: # Implement context manager exit function. self.close() - def connect(self, negotiate=True): + def connect(self, negotiate: bool = True) -> Optional[QMPMessage]: """ Connect to the QMP Monitor and perform capabilities negotiation. @@ -214,7 +224,7 @@ class QEMUMonitorProtocol: return self.__negotiate_capabilities() return None - def accept(self, timeout=15.0): + def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage: """ Await connection from QMP Monitor and perform capabilities negotiation. @@ -250,7 +260,9 @@ class QEMUMonitorProtocol: self.logger.debug("<<< %s", resp) return resp - def cmd(self, name, args=None, cmd_id=None): + def cmd(self, name: str, + args: Optional[Dict[str, Any]] = None, + cmd_id: Optional[Any] = None) -> QMPMessage: """ Build a QMP command and send it to the QMP Monitor. @@ -258,14 +270,14 @@ class QEMUMonitorProtocol: @param args: command arguments (dict) @param cmd_id: command id (dict, list, string or int) """ - qmp_cmd = {'execute': name} + qmp_cmd: QMPMessage = {'execute': name} if args: qmp_cmd['arguments'] = args if cmd_id: qmp_cmd['id'] = cmd_id return self.cmd_obj(qmp_cmd) - def command(self, cmd, **kwds): + def command(self, cmd: str, **kwds: Any) -> QMPReturnValue: """ Build and send a QMP command to the monitor, report errors if any """ @@ -278,7 +290,8 @@ class QEMUMonitorProtocol: ) return cast(QMPReturnValue, ret['return']) - def pull_event(self, wait=False): + def pull_event(self, + wait: Union[bool, float] = False) -> Optional[QMPMessage]: """ Pulls a single event. @@ -298,7 +311,7 @@ class QEMUMonitorProtocol: return self.__events.pop(0) return None - def get_events(self, wait=False): + def get_events(self, wait: bool = False) -> List[QMPMessage]: """ Get a list of available QMP events. @@ -315,13 +328,13 @@ class QEMUMonitorProtocol: self.__get_events(wait) return self.__events - def clear_events(self): + def clear_events(self) -> None: """ Clear current list of pending events. """ self.__events = [] - def close(self): + def close(self) -> None: """ Close the socket and socket file. """ @@ -330,16 +343,22 @@ class QEMUMonitorProtocol: if self.__sockfile: self.__sockfile.close() - def settimeout(self, timeout): + def settimeout(self, timeout: Optional[float]) -> None: """ Set the socket timeout. - @param timeout (float): timeout in seconds, or None. + @param timeout (float): timeout in seconds (non-zero), or None. @note This is a wrap around socket.settimeout + + @raise ValueError: if timeout was set to 0. """ + if timeout == 0: + msg = "timeout cannot be 0; this engages non-blocking mode." + msg += " Use 'None' instead to disable timeouts." + raise ValueError(msg) self.__sock.settimeout(timeout) - def get_sock_fd(self): + def get_sock_fd(self) -> int: """ Get the socket file descriptor. @@ -347,7 +366,7 @@ class QEMUMonitorProtocol: """ return self.__sock.fileno() - def is_scm_available(self): + def is_scm_available(self) -> bool: """ Check if the socket allows for SCM_RIGHTS. diff --git a/python/qemu/qtest.py b/python/qemu/qtest.py index 888c8bd2f6..39a0cf62fe 100644 --- a/python/qemu/qtest.py +++ b/python/qemu/qtest.py @@ -17,11 +17,17 @@ subclass of QEMUMachine, respectively. # Based on qmp.py. # -import socket import os -from typing import Optional, TextIO +import socket +from typing import ( + List, + Optional, + Sequence, + TextIO, +) from .machine import QEMUMachine +from .qmp import SocketAddrT class QEMUQtestProtocol: @@ -38,7 +44,8 @@ class QEMUQtestProtocol: No conection is estabalished by __init__(), this is done by the connect() or accept() methods. """ - def __init__(self, address, server=False): + def __init__(self, address: SocketAddrT, + server: bool = False): self._address = address self._sock = self._get_sock() self._sockfile: Optional[TextIO] = None @@ -46,14 +53,14 @@ class QEMUQtestProtocol: self._sock.bind(self._address) self._sock.listen(1) - def _get_sock(self): + def _get_sock(self) -> socket.socket: if isinstance(self._address, tuple): family = socket.AF_INET else: family = socket.AF_UNIX return socket.socket(family, socket.SOCK_STREAM) - def connect(self): + def connect(self) -> None: """ Connect to the qtest socket. @@ -62,7 +69,7 @@ class QEMUQtestProtocol: self._sock.connect(self._address) self._sockfile = self._sock.makefile(mode='r') - def accept(self): + def accept(self) -> None: """ Await connection from QEMU. @@ -71,7 +78,7 @@ class QEMUQtestProtocol: self._sock, _ = self._sock.accept() self._sockfile = self._sock.makefile(mode='r') - def cmd(self, qtest_cmd): + def cmd(self, qtest_cmd: str) -> str: """ Send a qtest command on the wire. @@ -82,14 +89,16 @@ class QEMUQtestProtocol: resp = self._sockfile.readline() return resp - def close(self): - """Close this socket.""" + def close(self) -> None: + """ + Close this socket. + """ self._sock.close() if self._sockfile: self._sockfile.close() self._sockfile = None - def settimeout(self, timeout): + def settimeout(self, timeout: Optional[float]) -> None: """Set a timeout, in seconds.""" self._sock.settimeout(timeout) @@ -99,8 +108,13 @@ class QEMUQtestMachine(QEMUMachine): A QEMU VM, with a qtest socket available. """ - def __init__(self, binary, args=None, name=None, test_dir="/var/tmp", - socket_scm_helper=None, sock_dir=None): + def __init__(self, + binary: str, + args: Sequence[str] = (), + name: Optional[str] = None, + test_dir: str = "/var/tmp", + socket_scm_helper: Optional[str] = None, + sock_dir: Optional[str] = None): if name is None: name = "qemu-%d" % os.getpid() if sock_dir is None: @@ -108,16 +122,19 @@ class QEMUQtestMachine(QEMUMachine): super().__init__(binary, args, name=name, test_dir=test_dir, socket_scm_helper=socket_scm_helper, sock_dir=sock_dir) - self._qtest = None + self._qtest: Optional[QEMUQtestProtocol] = None self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock") - def _base_args(self): - args = super()._base_args() - args.extend(['-qtest', 'unix:path=' + self._qtest_path, - '-accel', 'qtest']) + @property + def _base_args(self) -> List[str]: + args = super()._base_args + args.extend([ + '-qtest', f"unix:path={self._qtest_path}", + '-accel', 'qtest' + ]) return args - def _pre_launch(self): + def _pre_launch(self) -> None: super()._pre_launch() self._qtest = QEMUQtestProtocol(self._qtest_path, server=True) @@ -126,7 +143,7 @@ class QEMUQtestMachine(QEMUMachine): super()._post_launch() self._qtest.accept() - def _post_shutdown(self): + def _post_shutdown(self) -> None: super()._post_shutdown() self._remove_if_exists(self._qtest_path) diff --git a/qapi/machine.json b/qapi/machine.json index 756dacb06f..7c9a263778 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -453,6 +453,63 @@ { 'command': 'query-vm-generation-id', 'returns': 'GuidInfo' } ## +# @system_reset: +# +# Performs a hard reset of a guest. +# +# Since: 0.14.0 +# +# Example: +# +# -> { "execute": "system_reset" } +# <- { "return": {} } +# +## +{ 'command': 'system_reset' } + +## +# @system_powerdown: +# +# Requests that a guest perform a powerdown operation. +# +# Since: 0.14.0 +# +# Notes: A guest may or may not respond to this command. This command +# returning does not indicate that a guest has accepted the request or +# that it has shut down. Many guests will respond to this command by +# prompting the user in some way. +# Example: +# +# -> { "execute": "system_powerdown" } +# <- { "return": {} } +# +## +{ 'command': 'system_powerdown' } + +## +# @system_wakeup: +# +# Wake up guest from suspend. If the guest has wake-up from suspend +# support enabled (wakeup-suspend-support flag from +# query-current-machine), wake-up guest from suspend if the guest is +# in SUSPENDED state. Return an error otherwise. +# +# Since: 1.1 +# +# Returns: nothing. +# +# Note: prior to 4.0, this command does nothing in case the guest +# isn't suspended. +# +# Example: +# +# -> { "execute": "system_wakeup" } +# <- { "return": {} } +# +## +{ 'command': 'system_wakeup' } + +## # @LostTickPolicy: # # Policy for handling lost ticks in timer devices. Ticks end up getting @@ -485,6 +542,56 @@ 'data': ['discard', 'delay', 'slew' ] } ## +# @inject-nmi: +# +# Injects a Non-Maskable Interrupt into the default CPU (x86/s390) or all CPUs (ppc64). +# The command fails when the guest doesn't support injecting. +# +# Returns: If successful, nothing +# +# Since: 0.14.0 +# +# Note: prior to 2.1, this command was only supported for x86 and s390 VMs +# +# Example: +# +# -> { "execute": "inject-nmi" } +# <- { "return": {} } +# +## +{ 'command': 'inject-nmi' } + +## +# @KvmInfo: +# +# Information about support for KVM acceleration +# +# @enabled: true if KVM acceleration is active +# +# @present: true if KVM acceleration is built into this executable +# +# Since: 0.14.0 +## +{ 'struct': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} } + +## +# @query-kvm: +# +# Returns information about KVM acceleration +# +# Returns: @KvmInfo +# +# Since: 0.14.0 +# +# Example: +# +# -> { "execute": "query-kvm" } +# <- { "return": { "enabled": true, "present": true } } +# +## +{ 'command': 'query-kvm', 'returns': 'KvmInfo' } + +## # @NumaOptionsType: # # @node: NUMA nodes configuration @@ -811,6 +918,67 @@ 'data': [ 'default', 'preferred', 'bind', 'interleave' ] } ## +# @memsave: +# +# Save a portion of guest memory to a file. +# +# @val: the virtual address of the guest to start from +# +# @size: the size of memory region to save +# +# @filename: the file to save the memory to as binary data +# +# @cpu-index: the index of the virtual CPU to use for translating the +# virtual address (defaults to CPU 0) +# +# Returns: Nothing on success +# +# Since: 0.14.0 +# +# Notes: Errors were not reliably returned until 1.1 +# +# Example: +# +# -> { "execute": "memsave", +# "arguments": { "val": 10, +# "size": 100, +# "filename": "/tmp/virtual-mem-dump" } } +# <- { "return": {} } +# +## +{ 'command': 'memsave', + 'data': {'val': 'int', 'size': 'int', 'filename': 'str', '*cpu-index': 'int'} } + +## +# @pmemsave: +# +# Save a portion of guest physical memory to a file. +# +# @val: the physical address of the guest to start from +# +# @size: the size of memory region to save +# +# @filename: the file to save the memory to as binary data +# +# Returns: Nothing on success +# +# Since: 0.14.0 +# +# Notes: Errors were not reliably returned until 1.1 +# +# Example: +# +# -> { "execute": "pmemsave", +# "arguments": { "val": 10, +# "size": 100, +# "filename": "/tmp/physical-mem-dump" } } +# <- { "return": {} } +# +## +{ 'command': 'pmemsave', + 'data': {'val': 'int', 'size': 'int', 'filename': 'str'} } + +## # @Memdev: # # Information about memory backend diff --git a/qapi/migration.json b/qapi/migration.json index 974021a5c8..a5da513c9e 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -1552,6 +1552,47 @@ 'data': {'filename': 'str', '*live':'bool' } } ## +# @xen-set-global-dirty-log: +# +# Enable or disable the global dirty log mode. +# +# @enable: true to enable, false to disable. +# +# Returns: nothing +# +# Since: 1.3 +# +# Example: +# +# -> { "execute": "xen-set-global-dirty-log", +# "arguments": { "enable": true } } +# <- { "return": {} } +# +## +{ 'command': 'xen-set-global-dirty-log', 'data': { 'enable': 'bool' } } + +## +# @xen-load-devices-state: +# +# Load the state of all devices from file. The RAM and the block devices +# of the VM are not loaded by this command. +# +# @filename: the file to load the state of the devices from as binary +# data. See xen-save-devices-state.txt for a description of the binary +# format. +# +# Since: 2.7 +# +# Example: +# +# -> { "execute": "xen-load-devices-state", +# "arguments": { "filename": "/tmp/resume" } } +# <- { "return": {} } +# +## +{ 'command': 'xen-load-devices-state', 'data': {'filename': 'str'} } + +## # @xen-set-replication: # # Enable or disable replication. diff --git a/qapi/misc.json b/qapi/misc.json index 7d1e2e9aae..40df513856 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -69,36 +69,6 @@ { 'command': 'query-name', 'returns': 'NameInfo', 'allow-preconfig': true } ## -# @KvmInfo: -# -# Information about support for KVM acceleration -# -# @enabled: true if KVM acceleration is active -# -# @present: true if KVM acceleration is built into this executable -# -# Since: 0.14.0 -## -{ 'struct': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} } - -## -# @query-kvm: -# -# Returns information about KVM acceleration -# -# Returns: @KvmInfo -# -# Since: 0.14.0 -# -# Example: -# -# -> { "execute": "query-kvm" } -# <- { "return": { "enabled": true, "present": true } } -# -## -{ 'command': 'query-kvm', 'returns': 'KvmInfo' } - -## # @IOThreadInfo: # # Information about an iothread @@ -178,101 +148,6 @@ { 'command': 'stop' } ## -# @system_reset: -# -# Performs a hard reset of a guest. -# -# Since: 0.14.0 -# -# Example: -# -# -> { "execute": "system_reset" } -# <- { "return": {} } -# -## -{ 'command': 'system_reset' } - -## -# @system_powerdown: -# -# Requests that a guest perform a powerdown operation. -# -# Since: 0.14.0 -# -# Notes: A guest may or may not respond to this command. This command -# returning does not indicate that a guest has accepted the request or -# that it has shut down. Many guests will respond to this command by -# prompting the user in some way. -# Example: -# -# -> { "execute": "system_powerdown" } -# <- { "return": {} } -# -## -{ 'command': 'system_powerdown' } - -## -# @memsave: -# -# Save a portion of guest memory to a file. -# -# @val: the virtual address of the guest to start from -# -# @size: the size of memory region to save -# -# @filename: the file to save the memory to as binary data -# -# @cpu-index: the index of the virtual CPU to use for translating the -# virtual address (defaults to CPU 0) -# -# Returns: Nothing on success -# -# Since: 0.14.0 -# -# Notes: Errors were not reliably returned until 1.1 -# -# Example: -# -# -> { "execute": "memsave", -# "arguments": { "val": 10, -# "size": 100, -# "filename": "/tmp/virtual-mem-dump" } } -# <- { "return": {} } -# -## -{ 'command': 'memsave', - 'data': {'val': 'int', 'size': 'int', 'filename': 'str', '*cpu-index': 'int'} } - -## -# @pmemsave: -# -# Save a portion of guest physical memory to a file. -# -# @val: the physical address of the guest to start from -# -# @size: the size of memory region to save -# -# @filename: the file to save the memory to as binary data -# -# Returns: Nothing on success -# -# Since: 0.14.0 -# -# Notes: Errors were not reliably returned until 1.1 -# -# Example: -# -# -> { "execute": "pmemsave", -# "arguments": { "val": 10, -# "size": 100, -# "filename": "/tmp/physical-mem-dump" } } -# <- { "return": {} } -# -## -{ 'command': 'pmemsave', - 'data': {'val': 'int', 'size': 'int', 'filename': 'str'} } - -## # @cont: # # Resume guest VCPU execution. @@ -319,49 +194,6 @@ { 'command': 'x-exit-preconfig', 'allow-preconfig': true } ## -# @system_wakeup: -# -# Wake up guest from suspend. If the guest has wake-up from suspend -# support enabled (wakeup-suspend-support flag from -# query-current-machine), wake-up guest from suspend if the guest is -# in SUSPENDED state. Return an error otherwise. -# -# Since: 1.1 -# -# Returns: nothing. -# -# Note: prior to 4.0, this command does nothing in case the guest -# isn't suspended. -# -# Example: -# -# -> { "execute": "system_wakeup" } -# <- { "return": {} } -# -## -{ 'command': 'system_wakeup' } - -## -# @inject-nmi: -# -# Injects a Non-Maskable Interrupt into the default CPU (x86/s390) or all CPUs (ppc64). -# The command fails when the guest doesn't support injecting. -# -# Returns: If successful, nothing -# -# Since: 0.14.0 -# -# Note: prior to 2.1, this command was only supported for x86 and s390 VMs -# -# Example: -# -# -> { "execute": "inject-nmi" } -# <- { "return": {} } -# -## -{ 'command': 'inject-nmi' } - -## # @human-monitor-command: # # Execute a command on the human monitor and return the output. @@ -456,26 +288,6 @@ 'features': [ 'deprecated' ] } ## -# @xen-set-global-dirty-log: -# -# Enable or disable the global dirty log mode. -# -# @enable: true to enable, false to disable. -# -# Returns: nothing -# -# Since: 1.3 -# -# Example: -# -# -> { "execute": "xen-set-global-dirty-log", -# "arguments": { "enable": true } } -# <- { "return": {} } -# -## -{ 'command': 'xen-set-global-dirty-log', 'data': { 'enable': 'bool' } } - -## # @getfd: # # Receive a file descriptor via SCM rights and assign it a name @@ -756,24 +568,3 @@ 'data': { '*option': 'str' }, 'returns': ['CommandLineOptionInfo'], 'allow-preconfig': true } - -## -# @xen-load-devices-state: -# -# Load the state of all devices from file. The RAM and the block devices -# of the VM are not loaded by this command. -# -# @filename: the file to load the state of the devices from as binary -# data. See xen-save-devices-state.txt for a description of the binary -# format. -# -# Since: 2.7 -# -# Example: -# -# -> { "execute": "xen-load-devices-state", -# "arguments": { "filename": "/tmp/resume" } } -# <- { "return": {} } -# -## -{ 'command': 'xen-load-devices-state', 'data': {'filename': 'str'} } diff --git a/scripts/decodetree.py b/scripts/decodetree.py index 60fd3b5e5f..c1bf3cfa85 100644 --- a/scripts/decodetree.py +++ b/scripts/decodetree.py @@ -548,7 +548,7 @@ class Tree: output(ind, ' /* ', str_match_bits(innerbits, innermask), ' */\n') s.output_code(i + 4, extracted, innerbits, innermask) - output(ind, ' return false;\n') + output(ind, ' break;\n') output(ind, '}\n') # end Tree diff --git a/softmmu/cpus.c b/softmmu/cpus.c index 47cceddd80..e46ac68ad0 100644 --- a/softmmu/cpus.c +++ b/softmmu/cpus.c @@ -26,6 +26,7 @@ #include "qemu-common.h" #include "monitor/monitor.h" #include "qapi/error.h" +#include "qapi/qapi-commands-machine.h" #include "qapi/qapi-commands-misc.h" #include "qapi/qapi-events-run-state.h" #include "qapi/qmp/qerror.h" diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 056319859f..07492e9f9a 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -255,6 +255,15 @@ static void arm_cpu_reset(DeviceState *dev) uint8_t *rom; uint32_t vecbase; + if (cpu_isar_feature(aa32_lob, cpu)) { + /* + * LTPSIZE is constant 4 if MVE not implemented, and resets + * to an UNKNOWN value if MVE is implemented. We choose to + * always reset to 4. + */ + env->v7m.ltpsize = 4; + } + if (arm_feature(env, ARM_FEATURE_M_SECURITY)) { env->v7m.secure = true; } else { @@ -1429,17 +1438,22 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) u = cpu->isar.mvfr0; u = FIELD_DP32(u, MVFR0, FPSP, 0); u = FIELD_DP32(u, MVFR0, FPDP, 0); - u = FIELD_DP32(u, MVFR0, FPTRAP, 0); u = FIELD_DP32(u, MVFR0, FPDIVIDE, 0); u = FIELD_DP32(u, MVFR0, FPSQRT, 0); - u = FIELD_DP32(u, MVFR0, FPSHVEC, 0); u = FIELD_DP32(u, MVFR0, FPROUND, 0); + if (!arm_feature(env, ARM_FEATURE_M)) { + u = FIELD_DP32(u, MVFR0, FPTRAP, 0); + u = FIELD_DP32(u, MVFR0, FPSHVEC, 0); + } cpu->isar.mvfr0 = u; u = cpu->isar.mvfr1; u = FIELD_DP32(u, MVFR1, FPFTZ, 0); u = FIELD_DP32(u, MVFR1, FPDNAN, 0); u = FIELD_DP32(u, MVFR1, FPHP, 0); + if (arm_feature(env, ARM_FEATURE_M)) { + u = FIELD_DP32(u, MVFR1, FP16, 0); + } cpu->isar.mvfr1 = u; u = cpu->isar.mvfr2; @@ -1475,16 +1489,18 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) u = FIELD_DP32(u, ID_ISAR6, FHM, 0); cpu->isar.id_isar6 = u; - u = cpu->isar.mvfr1; - u = FIELD_DP32(u, MVFR1, SIMDLS, 0); - u = FIELD_DP32(u, MVFR1, SIMDINT, 0); - u = FIELD_DP32(u, MVFR1, SIMDSP, 0); - u = FIELD_DP32(u, MVFR1, SIMDHP, 0); - cpu->isar.mvfr1 = u; - - u = cpu->isar.mvfr2; - u = FIELD_DP32(u, MVFR2, SIMDMISC, 0); - cpu->isar.mvfr2 = u; + if (!arm_feature(env, ARM_FEATURE_M)) { + u = cpu->isar.mvfr1; + u = FIELD_DP32(u, MVFR1, SIMDLS, 0); + u = FIELD_DP32(u, MVFR1, SIMDINT, 0); + u = FIELD_DP32(u, MVFR1, SIMDSP, 0); + u = FIELD_DP32(u, MVFR1, SIMDHP, 0); + cpu->isar.mvfr1 = u; + + u = cpu->isar.mvfr2; + u = FIELD_DP32(u, MVFR2, SIMDMISC, 0); + cpu->isar.mvfr2 = u; + } } if (!cpu->has_neon && !cpu->has_vfp) { diff --git a/target/arm/cpu.h b/target/arm/cpu.h index cfff1b5c8f..49cd5cabcf 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -549,6 +549,7 @@ typedef struct CPUARMState { uint32_t fpdscr[M_REG_NUM_BANKS]; uint32_t cpacr[M_REG_NUM_BANKS]; uint32_t nsacr; + int ltpsize; } v7m; /* Information associated with an exception about to be taken: @@ -1985,6 +1986,7 @@ enum arm_features { ARM_FEATURE_VBAR, /* has cp15 VBAR */ ARM_FEATURE_M_SECURITY, /* M profile Security Extension */ ARM_FEATURE_M_MAIN, /* M profile Main Extension */ + ARM_FEATURE_V8_1M, /* M profile extras only in v8.1M and later */ }; static inline int arm_feature(CPUARMState *env, int feature) @@ -3472,6 +3474,12 @@ static inline bool isar_feature_aa32_arm_div(const ARMISARegisters *id) return FIELD_EX32(id->id_isar0, ID_ISAR0, DIVIDE) > 1; } +static inline bool isar_feature_aa32_lob(const ARMISARegisters *id) +{ + /* (M-profile) low-overhead loops and branch future */ + return FIELD_EX32(id->id_isar0, ID_ISAR0, CMPBRANCH) >= 3; +} + static inline bool isar_feature_aa32_jazelle(const ARMISARegisters *id) { return FIELD_EX32(id->id_isar1, ID_ISAR1, JAZELLE) != 0; diff --git a/target/arm/helper.c b/target/arm/helper.c index cd0779ff5f..97bb6b8c01 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -50,6 +50,7 @@ static bool get_phys_addr_lpae(CPUARMState *env, target_ulong address, #endif static void switch_mode(CPUARMState *env, int mode); +static int aa64_va_parameter_tbi(uint64_t tcr, ARMMMUIdx mmu_idx); static int vfp_gdb_get_reg(CPUARMState *env, GByteArray *buf, int reg) { @@ -4457,6 +4458,33 @@ static int vae1_tlbmask(CPUARMState *env) } } +/* Return 56 if TBI is enabled, 64 otherwise. */ +static int tlbbits_for_regime(CPUARMState *env, ARMMMUIdx mmu_idx, + uint64_t addr) +{ + uint64_t tcr = regime_tcr(env, mmu_idx)->raw_tcr; + int tbi = aa64_va_parameter_tbi(tcr, mmu_idx); + int select = extract64(addr, 55, 1); + + return (tbi >> select) & 1 ? 56 : 64; +} + +static int vae1_tlbbits(CPUARMState *env, uint64_t addr) +{ + ARMMMUIdx mmu_idx; + + /* Only the regime of the mmu_idx below is significant. */ + if (arm_is_secure_below_el3(env)) { + mmu_idx = ARMMMUIdx_SE10_0; + } else if ((env->cp15.hcr_el2 & (HCR_E2H | HCR_TGE)) + == (HCR_E2H | HCR_TGE)) { + mmu_idx = ARMMMUIdx_E20_0; + } else { + mmu_idx = ARMMMUIdx_E10_0; + } + return tlbbits_for_regime(env, mmu_idx, addr); +} + static void tlbi_aa64_vmalle1is_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { @@ -4593,8 +4621,9 @@ static void tlbi_aa64_vae1is_write(CPUARMState *env, const ARMCPRegInfo *ri, CPUState *cs = env_cpu(env); int mask = vae1_tlbmask(env); uint64_t pageaddr = sextract64(value << 12, 0, 56); + int bits = vae1_tlbbits(env, pageaddr); - tlb_flush_page_by_mmuidx_all_cpus_synced(cs, pageaddr, mask); + tlb_flush_page_bits_by_mmuidx_all_cpus_synced(cs, pageaddr, mask, bits); } static void tlbi_aa64_vae1_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -4608,11 +4637,12 @@ static void tlbi_aa64_vae1_write(CPUARMState *env, const ARMCPRegInfo *ri, CPUState *cs = env_cpu(env); int mask = vae1_tlbmask(env); uint64_t pageaddr = sextract64(value << 12, 0, 56); + int bits = vae1_tlbbits(env, pageaddr); if (tlb_force_broadcast(env)) { - tlb_flush_page_by_mmuidx_all_cpus_synced(cs, pageaddr, mask); + tlb_flush_page_bits_by_mmuidx_all_cpus_synced(cs, pageaddr, mask, bits); } else { - tlb_flush_page_by_mmuidx(cs, pageaddr, mask); + tlb_flush_page_bits_by_mmuidx(cs, pageaddr, mask, bits); } } @@ -4621,9 +4651,10 @@ static void tlbi_aa64_vae2is_write(CPUARMState *env, const ARMCPRegInfo *ri, { CPUState *cs = env_cpu(env); uint64_t pageaddr = sextract64(value << 12, 0, 56); + int bits = tlbbits_for_regime(env, ARMMMUIdx_E2, pageaddr); - tlb_flush_page_by_mmuidx_all_cpus_synced(cs, pageaddr, - ARMMMUIdxBit_E2); + tlb_flush_page_bits_by_mmuidx_all_cpus_synced(cs, pageaddr, + ARMMMUIdxBit_E2, bits); } static void tlbi_aa64_vae3is_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -4631,9 +4662,10 @@ static void tlbi_aa64_vae3is_write(CPUARMState *env, const ARMCPRegInfo *ri, { CPUState *cs = env_cpu(env); uint64_t pageaddr = sextract64(value << 12, 0, 56); + int bits = tlbbits_for_regime(env, ARMMMUIdx_SE3, pageaddr); - tlb_flush_page_by_mmuidx_all_cpus_synced(cs, pageaddr, - ARMMMUIdxBit_SE3); + tlb_flush_page_bits_by_mmuidx_all_cpus_synced(cs, pageaddr, + ARMMMUIdxBit_SE3, bits); } static CPAccessResult aa64_zva_access(CPUARMState *env, const ARMCPRegInfo *ri, @@ -6874,10 +6906,11 @@ static CPAccessResult access_mte(CPUARMState *env, const ARMCPRegInfo *ri, { int el = arm_current_el(env); - if (el < 2 && - arm_feature(env, ARM_FEATURE_EL2) && - !(arm_hcr_el2_eff(env) & HCR_ATA)) { - return CP_ACCESS_TRAP_EL2; + if (el < 2 && arm_feature(env, ARM_FEATURE_EL2)) { + uint64_t hcr = arm_hcr_el2_eff(env); + if (!(hcr & HCR_ATA) && (!(hcr & HCR_E2H) || !(hcr & HCR_TGE))) { + return CP_ACCESS_TRAP_EL2; + } } if (el < 3 && arm_feature(env, ARM_FEATURE_EL3) && diff --git a/target/arm/helper.h b/target/arm/helper.h index 8defd7c801..774d2cddb5 100644 --- a/target/arm/helper.h +++ b/target/arm/helper.h @@ -213,6 +213,19 @@ DEF_HELPER_3(vfp_ultoh, f16, i32, i32, ptr) DEF_HELPER_3(vfp_sqtoh, f16, i64, i32, ptr) DEF_HELPER_3(vfp_uqtoh, f16, i64, i32, ptr) +DEF_HELPER_3(vfp_shtos_round_to_nearest, f32, i32, i32, ptr) +DEF_HELPER_3(vfp_sltos_round_to_nearest, f32, i32, i32, ptr) +DEF_HELPER_3(vfp_uhtos_round_to_nearest, f32, i32, i32, ptr) +DEF_HELPER_3(vfp_ultos_round_to_nearest, f32, i32, i32, ptr) +DEF_HELPER_3(vfp_shtod_round_to_nearest, f64, i64, i32, ptr) +DEF_HELPER_3(vfp_sltod_round_to_nearest, f64, i64, i32, ptr) +DEF_HELPER_3(vfp_uhtod_round_to_nearest, f64, i64, i32, ptr) +DEF_HELPER_3(vfp_ultod_round_to_nearest, f64, i64, i32, ptr) +DEF_HELPER_3(vfp_shtoh_round_to_nearest, f16, i32, i32, ptr) +DEF_HELPER_3(vfp_uhtoh_round_to_nearest, f16, i32, i32, ptr) +DEF_HELPER_3(vfp_sltoh_round_to_nearest, f16, i32, i32, ptr) +DEF_HELPER_3(vfp_ultoh_round_to_nearest, f16, i32, i32, ptr) + DEF_HELPER_FLAGS_2(set_rmode, TCG_CALL_NO_RWG, i32, i32, ptr) DEF_HELPER_FLAGS_3(vfp_fcvt_f16_to_f32, TCG_CALL_NO_RWG, f32, f16, ptr, i32) diff --git a/target/arm/internals.h b/target/arm/internals.h index ae99725d2b..5460678756 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -1252,10 +1252,11 @@ static inline bool allocation_tag_access_enabled(CPUARMState *env, int el, && !(env->cp15.scr_el3 & SCR_ATA)) { return false; } - if (el < 2 - && arm_feature(env, ARM_FEATURE_EL2) - && !(arm_hcr_el2_eff(env) & HCR_ATA)) { - return false; + if (el < 2 && arm_feature(env, ARM_FEATURE_EL2)) { + uint64_t hcr = arm_hcr_el2_eff(env); + if (!(hcr & HCR_ATA) && (!(hcr & HCR_E2H) || !(hcr & HCR_TGE))) { + return false; + } } sctlr &= (el == 0 ? SCTLR_ATA0 : SCTLR_ATA); return sctlr != 0; diff --git a/target/arm/m-nocp.decode b/target/arm/m-nocp.decode index 7182d7d121..28c8ac6b94 100644 --- a/target/arm/m-nocp.decode +++ b/target/arm/m-nocp.decode @@ -29,14 +29,16 @@ # If the coprocessor is not present or disabled then we will generate # the NOCP exception; otherwise we let the insn through to the main decode. +&nocp cp + { # Special cases which do not take an early NOCP: VLLDM and VLSTM VLLDM_VLSTM 1110 1100 001 l:1 rn:4 0000 1010 0000 0000 # TODO: VSCCLRM (new in v8.1M) is similar: #VSCCLRM 1110 1100 1-01 1111 ---- 1011 ---- ---0 - NOCP 111- 1110 ---- ---- ---- cp:4 ---- ---- - NOCP 111- 110- ---- ---- ---- cp:4 ---- ---- - # TODO: From v8.1M onwards we will also want this range to NOCP - #NOCP_8_1 111- 1111 ---- ---- ---- ---- ---- ---- cp=10 + NOCP 111- 1110 ---- ---- ---- cp:4 ---- ---- &nocp + NOCP 111- 110- ---- ---- ---- cp:4 ---- ---- &nocp + # From v8.1M onwards this range will also NOCP: + NOCP_8_1 111- 1111 ---- ---- ---- ---- ---- ---- &nocp cp=10 } diff --git a/target/arm/mte_helper.c b/target/arm/mte_helper.c index 5615c6706c..153bd1e9df 100644 --- a/target/arm/mte_helper.c +++ b/target/arm/mte_helper.c @@ -525,14 +525,10 @@ static void mte_check_fail(CPUARMState *env, uint32_t desc, reg_el = regime_el(env, arm_mmu_idx); sctlr = env->cp15.sctlr_el[reg_el]; - switch (arm_mmu_idx) { - case ARMMMUIdx_E10_0: - case ARMMMUIdx_E20_0: - el = 0; + el = arm_current_el(env); + if (el == 0) { tcf = extract64(sctlr, 38, 2); - break; - default: - el = reg_el; + } else { tcf = extract64(sctlr, 40, 2); } @@ -563,8 +559,7 @@ static void mte_check_fail(CPUARMState *env, uint32_t desc, case 2: /* Tag check fail causes asynchronous flag set. */ - mmu_idx = arm_mmu_idx_el(env, el); - if (regime_has_2_ranges(mmu_idx)) { + if (regime_has_2_ranges(arm_mmu_idx)) { select = extract64(dirty_ptr, 55, 1); } else { select = 0; diff --git a/target/arm/t32.decode b/target/arm/t32.decode index 7069d821fd..8152739b52 100644 --- a/target/arm/t32.decode +++ b/target/arm/t32.decode @@ -90,6 +90,9 @@ SBC_rrri 1110101 1011 . .... 0 ... .... .... .... @s_rrr_shi } RSB_rrri 1110101 1110 . .... 0 ... .... .... .... @s_rrr_shi +# v8.1M CSEL and friends +CSEL 1110101 0010 1 rn:4 10 op:2 rd:4 fcond:4 rm:4 + # Data-processing (register-shifted register) MOV_rxrr 1111 1010 0 shty:2 s:1 rm:4 1111 rd:4 0000 rs:4 \ @@ -293,8 +296,8 @@ CLZ 1111 1010 1011 ---- 1111 .... 1000 .... @rdm { # Group insn[25:23] = 111, which is cond=111x for the branch below, # or unconditional, which would be illegal for the branch. - { - # Hints + [ + # Hints, and CPS { YIELD 1111 0011 1010 1111 1000 0000 0000 0001 WFE 1111 0011 1010 1111 1000 0000 0000 0010 @@ -307,20 +310,18 @@ CLZ 1111 1010 1011 ---- 1111 .... 1000 .... @rdm # The canonical nop ends in 0000 0000, but the whole rest # of the space is "reserved hint, behaves as nop". NOP 1111 0011 1010 1111 1000 0000 ---- ---- - } - # If imod == '00' && M == '0' then SEE "Hint instructions", above. - CPS 1111 0011 1010 1111 1000 0 imod:2 M:1 A:1 I:1 F:1 mode:5 \ + # If imod == '00' && M == '0' then SEE "Hint instructions", above. + CPS 1111 0011 1010 1111 1000 0 imod:2 M:1 A:1 I:1 F:1 mode:5 \ &cps + } # Miscellaneous control - [ - CLREX 1111 0011 1011 1111 1000 1111 0010 1111 - DSB 1111 0011 1011 1111 1000 1111 0100 ---- - DMB 1111 0011 1011 1111 1000 1111 0101 ---- - ISB 1111 0011 1011 1111 1000 1111 0110 ---- - SB 1111 0011 1011 1111 1000 1111 0111 0000 - ] + CLREX 1111 0011 1011 1111 1000 1111 0010 1111 + DSB 1111 0011 1011 1111 1000 1111 0100 ---- + DMB 1111 0011 1011 1111 1000 1111 0101 ---- + ISB 1111 0011 1011 1111 1000 1111 0110 ---- + SB 1111 0011 1011 1111 1000 1111 0111 0000 # Note that the v7m insn overlaps both the normal and banked insn. { @@ -348,7 +349,7 @@ CLZ 1111 1010 1011 ---- 1111 .... 1000 .... @rdm HVC 1111 0111 1110 .... 1000 .... .... .... \ &i imm=%imm16_16_0 UDF 1111 0111 1111 ---- 1010 ---- ---- ---- - } + ] B_cond_thumb 1111 0. cond:4 ...... 10.0 ............ &ci imm=%imm21 } @@ -647,4 +648,23 @@ MRC 1110 1110 ... 1 .... .... .... ... 1 .... @mcr B 1111 0. .......... 10.1 ............ @branch24 BL 1111 0. .......... 11.1 ............ @branch24 -BLX_i 1111 0. .......... 11.0 ............ @branch24 +{ + # BLX_i is non-M-profile only + BLX_i 1111 0. .......... 11.0 ............ @branch24 + # M-profile only: loop and branch insns + [ + # All these BF insns have boff != 0b0000; we NOP them all + BF 1111 0 boff:4 ------- 1100 - ---------- 1 # BFL + BF 1111 0 boff:4 0 ------ 1110 - ---------- 1 # BFCSEL + BF 1111 0 boff:4 10 ----- 1110 - ---------- 1 # BF + BF 1111 0 boff:4 11 ----- 1110 0 0000000000 1 # BFX, BFLX + ] + [ + # LE and WLS immediate + %lob_imm 1:10 11:1 !function=times_2 + + DLS 1111 0 0000 100 rn:4 1110 0000 0000 0001 + WLS 1111 0 0000 100 rn:4 1100 . .......... 1 imm=%lob_imm + LE 1111 0 0000 0 f:1 0 1111 1100 . .......... 1 imm=%lob_imm + ] +} diff --git a/target/arm/translate-vfp.c.inc b/target/arm/translate-vfp.c.inc index 28e0dba5f1..a7ed9bc81b 100644 --- a/target/arm/translate-vfp.c.inc +++ b/target/arm/translate-vfp.c.inc @@ -3141,16 +3141,16 @@ static bool trans_VCVT_fix_hp(DisasContext *s, arg_VCVT_fix_sp *a) /* Switch on op:U:sx bits */ switch (a->opc) { case 0: - gen_helper_vfp_shtoh(vd, vd, shift, fpst); + gen_helper_vfp_shtoh_round_to_nearest(vd, vd, shift, fpst); break; case 1: - gen_helper_vfp_sltoh(vd, vd, shift, fpst); + gen_helper_vfp_sltoh_round_to_nearest(vd, vd, shift, fpst); break; case 2: - gen_helper_vfp_uhtoh(vd, vd, shift, fpst); + gen_helper_vfp_uhtoh_round_to_nearest(vd, vd, shift, fpst); break; case 3: - gen_helper_vfp_ultoh(vd, vd, shift, fpst); + gen_helper_vfp_ultoh_round_to_nearest(vd, vd, shift, fpst); break; case 4: gen_helper_vfp_toshh_round_to_zero(vd, vd, shift, fpst); @@ -3200,16 +3200,16 @@ static bool trans_VCVT_fix_sp(DisasContext *s, arg_VCVT_fix_sp *a) /* Switch on op:U:sx bits */ switch (a->opc) { case 0: - gen_helper_vfp_shtos(vd, vd, shift, fpst); + gen_helper_vfp_shtos_round_to_nearest(vd, vd, shift, fpst); break; case 1: - gen_helper_vfp_sltos(vd, vd, shift, fpst); + gen_helper_vfp_sltos_round_to_nearest(vd, vd, shift, fpst); break; case 2: - gen_helper_vfp_uhtos(vd, vd, shift, fpst); + gen_helper_vfp_uhtos_round_to_nearest(vd, vd, shift, fpst); break; case 3: - gen_helper_vfp_ultos(vd, vd, shift, fpst); + gen_helper_vfp_ultos_round_to_nearest(vd, vd, shift, fpst); break; case 4: gen_helper_vfp_toshs_round_to_zero(vd, vd, shift, fpst); @@ -3265,16 +3265,16 @@ static bool trans_VCVT_fix_dp(DisasContext *s, arg_VCVT_fix_dp *a) /* Switch on op:U:sx bits */ switch (a->opc) { case 0: - gen_helper_vfp_shtod(vd, vd, shift, fpst); + gen_helper_vfp_shtod_round_to_nearest(vd, vd, shift, fpst); break; case 1: - gen_helper_vfp_sltod(vd, vd, shift, fpst); + gen_helper_vfp_sltod_round_to_nearest(vd, vd, shift, fpst); break; case 2: - gen_helper_vfp_uhtod(vd, vd, shift, fpst); + gen_helper_vfp_uhtod_round_to_nearest(vd, vd, shift, fpst); break; case 3: - gen_helper_vfp_ultod(vd, vd, shift, fpst); + gen_helper_vfp_ultod_round_to_nearest(vd, vd, shift, fpst); break; case 4: gen_helper_vfp_toshd_round_to_zero(vd, vd, shift, fpst); @@ -3459,7 +3459,7 @@ static bool trans_VLLDM_VLSTM(DisasContext *s, arg_VLLDM_VLSTM *a) return true; } -static bool trans_NOCP(DisasContext *s, arg_NOCP *a) +static bool trans_NOCP(DisasContext *s, arg_nocp *a) { /* * Handle M-profile early check for disabled coprocessor: @@ -3472,7 +3472,11 @@ static bool trans_NOCP(DisasContext *s, arg_NOCP *a) if (a->cp == 11) { a->cp = 10; } - /* TODO: in v8.1M cp 8, 9, 14, 15 also are governed by the cp10 enable */ + if (arm_dc_feature(s, ARM_FEATURE_V8_1M) && + (a->cp == 8 || a->cp == 9 || a->cp == 14 || a->cp == 15)) { + /* in v8.1M cp 8, 9, 14, 15 also are governed by the cp10 enable */ + a->cp = 10; + } if (a->cp != 10) { gen_exception_insn(s, s->pc_curr, EXCP_NOCP, @@ -3489,6 +3493,15 @@ static bool trans_NOCP(DisasContext *s, arg_NOCP *a) return false; } +static bool trans_NOCP_8_1(DisasContext *s, arg_nocp *a) +{ + /* This range needs a coprocessor check for v8.1M and later only */ + if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) { + return false; + } + return trans_NOCP(s, a); +} + static bool trans_VINS(DisasContext *s, arg_VINS *a) { TCGv_i32 rd, rm; diff --git a/target/arm/translate.c b/target/arm/translate.c index d34c1d351a..38371db540 100644 --- a/target/arm/translate.c +++ b/target/arm/translate.c @@ -2490,17 +2490,23 @@ static void gen_goto_tb(DisasContext *s, int n, target_ulong dest) s->base.is_jmp = DISAS_NORETURN; } -static inline void gen_jmp (DisasContext *s, uint32_t dest) +/* Jump, specifying which TB number to use if we gen_goto_tb() */ +static inline void gen_jmp_tb(DisasContext *s, uint32_t dest, int tbno) { if (unlikely(is_singlestepping(s))) { /* An indirect jump so that we still trigger the debug exception. */ gen_set_pc_im(s, dest); s->base.is_jmp = DISAS_JUMP; } else { - gen_goto_tb(s, 0, dest); + gen_goto_tb(s, tbno, dest); } } +static inline void gen_jmp(DisasContext *s, uint32_t dest) +{ + gen_jmp_tb(s, dest, 0); +} + static inline void gen_mulxy(TCGv_i32 t0, TCGv_i32 t1, int x, int y) { if (x) @@ -7401,22 +7407,60 @@ static bool op_smlad(DisasContext *s, arg_rrrr *a, bool m_swap, bool sub) gen_smul_dual(t1, t2); if (sub) { - /* This subtraction cannot overflow. */ - tcg_gen_sub_i32(t1, t1, t2); - } else { /* - * This addition cannot overflow 32 bits; however it may - * overflow considered as a signed operation, in which case - * we must set the Q flag. + * This subtraction cannot overflow, so we can do a simple + * 32-bit subtraction and then a possible 32-bit saturating + * addition of Ra. */ - gen_helper_add_setq(t1, cpu_env, t1, t2); - } - tcg_temp_free_i32(t2); + tcg_gen_sub_i32(t1, t1, t2); + tcg_temp_free_i32(t2); - if (a->ra != 15) { - t2 = load_reg(s, a->ra); + if (a->ra != 15) { + t2 = load_reg(s, a->ra); + gen_helper_add_setq(t1, cpu_env, t1, t2); + tcg_temp_free_i32(t2); + } + } else if (a->ra == 15) { + /* Single saturation-checking addition */ gen_helper_add_setq(t1, cpu_env, t1, t2); tcg_temp_free_i32(t2); + } else { + /* + * We need to add the products and Ra together and then + * determine whether the final result overflowed. Doing + * this as two separate add-and-check-overflow steps incorrectly + * sets Q for cases like (-32768 * -32768) + (-32768 * -32768) + -1. + * Do all the arithmetic at 64-bits and then check for overflow. + */ + TCGv_i64 p64, q64; + TCGv_i32 t3, qf, one; + + p64 = tcg_temp_new_i64(); + q64 = tcg_temp_new_i64(); + tcg_gen_ext_i32_i64(p64, t1); + tcg_gen_ext_i32_i64(q64, t2); + tcg_gen_add_i64(p64, p64, q64); + load_reg_var(s, t2, a->ra); + tcg_gen_ext_i32_i64(q64, t2); + tcg_gen_add_i64(p64, p64, q64); + tcg_temp_free_i64(q64); + + tcg_gen_extr_i64_i32(t1, t2, p64); + tcg_temp_free_i64(p64); + /* + * t1 is the low half of the result which goes into Rd. + * We have overflow and must set Q if the high half (t2) + * is different from the sign-extension of t1. + */ + t3 = tcg_temp_new_i32(); + tcg_gen_sari_i32(t3, t1, 31); + qf = load_cpu_field(QF); + one = tcg_const_i32(1); + tcg_gen_movcond_i32(TCG_COND_NE, qf, t2, t3, one, qf); + store_cpu_field(qf, QF); + tcg_temp_free_i32(one); + tcg_temp_free_i32(t3); + tcg_temp_free_i32(t2); } store_reg(s, a->rd, t1); return true; @@ -7880,6 +7924,14 @@ static bool trans_BLX_i(DisasContext *s, arg_BLX_i *a) { TCGv_i32 tmp; + /* + * BLX <imm> would be useless on M-profile; the encoding space + * is used for other insns from v8.1M onward, and UNDEFs before that. + */ + if (arm_dc_feature(s, ARM_FEATURE_M)) { + return false; + } + /* For A32, ARM_FEATURE_V5 is checked near the start of the uncond block. */ if (s->thumb && (a->imm & 2)) { return false; @@ -7925,6 +7977,109 @@ static bool trans_BLX_suffix(DisasContext *s, arg_BLX_suffix *a) return true; } +static bool trans_BF(DisasContext *s, arg_BF *a) +{ + /* + * M-profile branch future insns. The architecture permits an + * implementation to implement these as NOPs (equivalent to + * discarding the LO_BRANCH_INFO cache immediately), and we + * take that IMPDEF option because for QEMU a "real" implementation + * would be complicated and wouldn't execute any faster. + */ + if (!dc_isar_feature(aa32_lob, s)) { + return false; + } + if (a->boff == 0) { + /* SEE "Related encodings" (loop insns) */ + return false; + } + /* Handle as NOP */ + return true; +} + +static bool trans_DLS(DisasContext *s, arg_DLS *a) +{ + /* M-profile low-overhead loop start */ + TCGv_i32 tmp; + + if (!dc_isar_feature(aa32_lob, s)) { + return false; + } + if (a->rn == 13 || a->rn == 15) { + /* CONSTRAINED UNPREDICTABLE: we choose to UNDEF */ + return false; + } + + /* Not a while loop, no tail predication: just set LR to the count */ + tmp = load_reg(s, a->rn); + store_reg(s, 14, tmp); + return true; +} + +static bool trans_WLS(DisasContext *s, arg_WLS *a) +{ + /* M-profile low-overhead while-loop start */ + TCGv_i32 tmp; + TCGLabel *nextlabel; + + if (!dc_isar_feature(aa32_lob, s)) { + return false; + } + if (a->rn == 13 || a->rn == 15) { + /* CONSTRAINED UNPREDICTABLE: we choose to UNDEF */ + return false; + } + if (s->condexec_mask) { + /* + * WLS in an IT block is CONSTRAINED UNPREDICTABLE; + * we choose to UNDEF, because otherwise our use of + * gen_goto_tb(1) would clash with the use of TB exit 1 + * in the dc->condjmp condition-failed codepath in + * arm_tr_tb_stop() and we'd get an assertion. + */ + return false; + } + nextlabel = gen_new_label(); + tcg_gen_brcondi_i32(TCG_COND_EQ, cpu_R[a->rn], 0, nextlabel); + tmp = load_reg(s, a->rn); + store_reg(s, 14, tmp); + gen_jmp_tb(s, s->base.pc_next, 1); + + gen_set_label(nextlabel); + gen_jmp(s, read_pc(s) + a->imm); + return true; +} + +static bool trans_LE(DisasContext *s, arg_LE *a) +{ + /* + * M-profile low-overhead loop end. The architecture permits an + * implementation to discard the LO_BRANCH_INFO cache at any time, + * and we take the IMPDEF option to never set it in the first place + * (equivalent to always discarding it immediately), because for QEMU + * a "real" implementation would be complicated and wouldn't execute + * any faster. + */ + TCGv_i32 tmp; + + if (!dc_isar_feature(aa32_lob, s)) { + return false; + } + + if (!a->f) { + /* Not loop-forever. If LR <= 1 this is the last loop: do nothing. */ + arm_gen_condlabel(s); + tcg_gen_brcondi_i32(TCG_COND_LEU, cpu_R[14], 1, s->condlabel); + /* Decrement LR */ + tmp = load_reg(s, 14); + tcg_gen_addi_i32(tmp, tmp, -1); + store_reg(s, 14, tmp); + } + /* Jump back to the loop start */ + gen_jmp(s, read_pc(s) - a->imm); + return true; +} + static bool op_tbranch(DisasContext *s, arg_tbranch *a, bool half) { TCGv_i32 addr, tmp; @@ -8224,6 +8379,66 @@ static bool trans_IT(DisasContext *s, arg_IT *a) return true; } +/* v8.1M CSEL/CSINC/CSNEG/CSINV */ +static bool trans_CSEL(DisasContext *s, arg_CSEL *a) +{ + TCGv_i32 rn, rm, zero; + DisasCompare c; + + if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) { + return false; + } + + if (a->rm == 13) { + /* SEE "Related encodings" (MVE shifts) */ + return false; + } + + if (a->rd == 13 || a->rd == 15 || a->rn == 13 || a->fcond >= 14) { + /* CONSTRAINED UNPREDICTABLE: we choose to UNDEF */ + return false; + } + + /* In this insn input reg fields of 0b1111 mean "zero", not "PC" */ + if (a->rn == 15) { + rn = tcg_const_i32(0); + } else { + rn = load_reg(s, a->rn); + } + if (a->rm == 15) { + rm = tcg_const_i32(0); + } else { + rm = load_reg(s, a->rm); + } + + switch (a->op) { + case 0: /* CSEL */ + break; + case 1: /* CSINC */ + tcg_gen_addi_i32(rm, rm, 1); + break; + case 2: /* CSINV */ + tcg_gen_not_i32(rm, rm); + break; + case 3: /* CSNEG */ + tcg_gen_neg_i32(rm, rm); + break; + default: + g_assert_not_reached(); + } + + arm_test_cc(&c, a->fcond); + zero = tcg_const_i32(0); + tcg_gen_movcond_i32(c.cond, rn, c.value, zero, rn, rm); + arm_free_cc(&c); + tcg_temp_free_i32(zero); + + store_reg(s, a->rd, rn); + tcg_temp_free_i32(rm); + + return true; +} + /* * Legacy decoder. */ diff --git a/target/arm/vfp_helper.c b/target/arm/vfp_helper.c index 5666393ef7..01b9d8557f 100644 --- a/target/arm/vfp_helper.c +++ b/target/arm/vfp_helper.c @@ -174,6 +174,12 @@ uint32_t HELPER(vfp_get_fpscr)(CPUARMState *env) | (env->vfp.vec_len << 16) | (env->vfp.vec_stride << 20); + /* + * M-profile LTPSIZE overlaps A-profile Stride; whichever of the + * two is not applicable to this CPU will always be zero. + */ + fpscr |= env->v7m.ltpsize << 16; + fpscr |= vfp_get_fpscr_from_host(env); i = env->vfp.qc[0] | env->vfp.qc[1] | env->vfp.qc[2] | env->vfp.qc[3]; @@ -194,36 +200,45 @@ void HELPER(vfp_set_fpscr)(CPUARMState *env, uint32_t val) val &= ~FPCR_FZ16; } - if (arm_feature(env, ARM_FEATURE_M)) { + vfp_set_fpscr_to_host(env, val); + + if (!arm_feature(env, ARM_FEATURE_M)) { /* - * M profile FPSCR is RES0 for the QC, STRIDE, FZ16, LEN bits - * and also for the trapped-exception-handling bits IxE. + * Short-vector length and stride; on M-profile these bits + * are used for different purposes. + * We can't make this conditional be "if MVFR0.FPShVec != 0", + * because in v7A no-short-vector-support cores still had to + * allow Stride/Len to be written with the only effect that + * some insns are required to UNDEF if the guest sets them. + * + * TODO: if M-profile MVE implemented, set LTPSIZE. */ - val &= 0xf7c0009f; + env->vfp.vec_len = extract32(val, 16, 3); + env->vfp.vec_stride = extract32(val, 20, 2); } - vfp_set_fpscr_to_host(env, val); + if (arm_feature(env, ARM_FEATURE_NEON)) { + /* + * The bit we set within fpscr_q is arbitrary; the register as a + * whole being zero/non-zero is what counts. + * TODO: M-profile MVE also has a QC bit. + */ + env->vfp.qc[0] = val & FPCR_QC; + env->vfp.qc[1] = 0; + env->vfp.qc[2] = 0; + env->vfp.qc[3] = 0; + } /* * We don't implement trapped exception handling, so the * trap enable bits, IDE|IXE|UFE|OFE|DZE|IOE are all RAZ/WI (not RES0!) * - * If we exclude the exception flags, IOC|DZC|OFC|UFC|IXC|IDC - * (which are stored in fp_status), and the other RES0 bits - * in between, then we clear all of the low 16 bits. + * The exception flags IOC|DZC|OFC|UFC|IXC|IDC are stored in + * fp_status; QC, Len and Stride are stored separately earlier. + * Clear out all of those and the RES0 bits: only NZCV, AHP, DN, + * FZ, RMode and FZ16 are kept in vfp.xregs[FPSCR]. */ env->vfp.xregs[ARM_VFP_FPSCR] = val & 0xf7c80000; - env->vfp.vec_len = (val >> 16) & 7; - env->vfp.vec_stride = (val >> 20) & 3; - - /* - * The bit we set within fpscr_q is arbitrary; the register as a - * whole being zero/non-zero is what counts. - */ - env->vfp.qc[0] = val & FPCR_QC; - env->vfp.qc[1] = 0; - env->vfp.qc[2] = 0; - env->vfp.qc[3] = 0; } void vfp_set_fpscr(CPUARMState *env, uint32_t val) @@ -393,12 +408,32 @@ float32 VFP_HELPER(fcvts, d)(float64 x, CPUARMState *env) return float64_to_float32(x, &env->vfp.fp_status); } -/* VFP3 fixed point conversion. */ +/* + * VFP3 fixed point conversion. The AArch32 versions of fix-to-float + * must always round-to-nearest; the AArch64 ones honour the FPSCR + * rounding mode. (For AArch32 Neon the standard-FPSCR is set to + * round-to-nearest so either helper will work.) AArch32 float-to-fix + * must round-to-zero. + */ #define VFP_CONV_FIX_FLOAT(name, p, fsz, ftype, isz, itype) \ ftype HELPER(vfp_##name##to##p)(uint##isz##_t x, uint32_t shift, \ void *fpstp) \ { return itype##_to_##float##fsz##_scalbn(x, -shift, fpstp); } +#define VFP_CONV_FIX_FLOAT_ROUND(name, p, fsz, ftype, isz, itype) \ + ftype HELPER(vfp_##name##to##p##_round_to_nearest)(uint##isz##_t x, \ + uint32_t shift, \ + void *fpstp) \ + { \ + ftype ret; \ + float_status *fpst = fpstp; \ + FloatRoundMode oldmode = fpst->float_rounding_mode; \ + fpst->float_rounding_mode = float_round_nearest_even; \ + ret = itype##_to_##float##fsz##_scalbn(x, -shift, fpstp); \ + fpst->float_rounding_mode = oldmode; \ + return ret; \ + } + #define VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, ROUND, suff) \ uint##isz##_t HELPER(vfp_to##name##p##suff)(ftype x, uint32_t shift, \ void *fpst) \ @@ -412,6 +447,7 @@ uint##isz##_t HELPER(vfp_to##name##p##suff)(ftype x, uint32_t shift, \ #define VFP_CONV_FIX(name, p, fsz, ftype, isz, itype) \ VFP_CONV_FIX_FLOAT(name, p, fsz, ftype, isz, itype) \ +VFP_CONV_FIX_FLOAT_ROUND(name, p, fsz, ftype, isz, itype) \ VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, \ float_round_to_zero, _round_to_zero) \ VFP_CONV_FLOAT_FIX_ROUND(name, p, fsz, ftype, isz, itype, \ diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index f212cec446..63d2ace93c 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -605,7 +605,7 @@ class VM(qtest.QEMUQtestMachine): def hmp(self, command_line: str, use_log: bool = False) -> QMPMessage: cmd = 'human-monitor-command' - kwargs = {'command-line': command_line} + kwargs: Dict[str, Any] = {'command-line': command_line} if use_log: return self.qmp_log(cmd, **kwargs) else: diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 3987f96086..28d4068718 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -138,6 +138,7 @@ qtests_arm = \ ['arm-cpu-features', 'microbit-test', 'm25p80-test', + 'npcm7xx_timer-test', 'test-arm-mptimer', 'boot-serial-test', 'hexloader-test'] diff --git a/tests/qtest/npcm7xx_timer-test.c b/tests/qtest/npcm7xx_timer-test.c new file mode 100644 index 0000000000..f08b0cd62a --- /dev/null +++ b/tests/qtest/npcm7xx_timer-test.c @@ -0,0 +1,562 @@ +/* + * QTest testcase for the Nuvoton NPCM7xx Timer + * + * Copyright 2020 Google LLC + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "libqtest-single.h" + +#define TIM_REF_HZ (25000000) + +/* Bits in TCSRx */ +#define CEN BIT(30) +#define IE BIT(29) +#define MODE_ONESHOT (0 << 27) +#define MODE_PERIODIC (1 << 27) +#define CRST BIT(26) +#define CACT BIT(25) +#define PRESCALE(x) (x) + +/* Registers shared between all timers in a module. */ +#define TISR 0x18 +#define WTCR 0x1c +# define WTCLK(x) ((x) << 10) + +/* Power-on default; used to re-initialize timers before each test. */ +#define TCSR_DEFAULT PRESCALE(5) + +/* Register offsets for a timer within a timer block. */ +typedef struct Timer { + unsigned int tcsr_offset; + unsigned int ticr_offset; + unsigned int tdr_offset; +} Timer; + +/* A timer block containing 5 timers. */ +typedef struct TimerBlock { + int irq_base; + uint64_t base_addr; +} TimerBlock; + +/* Testdata for testing a particular timer within a timer block. */ +typedef struct TestData { + const TimerBlock *tim; + const Timer *timer; +} TestData; + +const TimerBlock timer_block[] = { + { + .irq_base = 32, + .base_addr = 0xf0008000, + }, + { + .irq_base = 37, + .base_addr = 0xf0009000, + }, + { + .irq_base = 42, + .base_addr = 0xf000a000, + }, +}; + +const Timer timer[] = { + { + .tcsr_offset = 0x00, + .ticr_offset = 0x08, + .tdr_offset = 0x10, + }, { + .tcsr_offset = 0x04, + .ticr_offset = 0x0c, + .tdr_offset = 0x14, + }, { + .tcsr_offset = 0x20, + .ticr_offset = 0x28, + .tdr_offset = 0x30, + }, { + .tcsr_offset = 0x24, + .ticr_offset = 0x2c, + .tdr_offset = 0x34, + }, { + .tcsr_offset = 0x40, + .ticr_offset = 0x48, + .tdr_offset = 0x50, + }, +}; + +/* Returns the index of the timer block. */ +static int tim_index(const TimerBlock *tim) +{ + ptrdiff_t diff = tim - timer_block; + + g_assert(diff >= 0 && diff < ARRAY_SIZE(timer_block)); + + return diff; +} + +/* Returns the index of a timer within a timer block. */ +static int timer_index(const Timer *t) +{ + ptrdiff_t diff = t - timer; + + g_assert(diff >= 0 && diff < ARRAY_SIZE(timer)); + + return diff; +} + +/* Returns the irq line for a given timer. */ +static int tim_timer_irq(const TestData *td) +{ + return td->tim->irq_base + timer_index(td->timer); +} + +/* Register read/write accessors. */ + +static void tim_write(const TestData *td, + unsigned int offset, uint32_t value) +{ + writel(td->tim->base_addr + offset, value); +} + +static uint32_t tim_read(const TestData *td, unsigned int offset) +{ + return readl(td->tim->base_addr + offset); +} + +static void tim_write_tcsr(const TestData *td, uint32_t value) +{ + tim_write(td, td->timer->tcsr_offset, value); +} + +static uint32_t tim_read_tcsr(const TestData *td) +{ + return tim_read(td, td->timer->tcsr_offset); +} + +static void tim_write_ticr(const TestData *td, uint32_t value) +{ + tim_write(td, td->timer->ticr_offset, value); +} + +static uint32_t tim_read_ticr(const TestData *td) +{ + return tim_read(td, td->timer->ticr_offset); +} + +static uint32_t tim_read_tdr(const TestData *td) +{ + return tim_read(td, td->timer->tdr_offset); +} + +/* Returns the number of nanoseconds to count the given number of cycles. */ +static int64_t tim_calculate_step(uint32_t count, uint32_t prescale) +{ + return (1000000000LL / TIM_REF_HZ) * count * (prescale + 1); +} + +/* Returns a bitmask corresponding to the timer under test. */ +static uint32_t tim_timer_bit(const TestData *td) +{ + return BIT(timer_index(td->timer)); +} + +/* Resets all timers to power-on defaults. */ +static void tim_reset(const TestData *td) +{ + int i, j; + + /* Reset all the timers, in case a previous test left a timer running. */ + for (i = 0; i < ARRAY_SIZE(timer_block); i++) { + for (j = 0; j < ARRAY_SIZE(timer); j++) { + writel(timer_block[i].base_addr + timer[j].tcsr_offset, + CRST | TCSR_DEFAULT); + } + writel(timer_block[i].base_addr + TISR, -1); + } +} + +/* Verifies the reset state of a timer. */ +static void test_reset(gconstpointer test_data) +{ + const TestData *td = test_data; + + tim_reset(td); + + g_assert_cmphex(tim_read_tcsr(td), ==, TCSR_DEFAULT); + g_assert_cmphex(tim_read_ticr(td), ==, 0); + g_assert_cmphex(tim_read_tdr(td), ==, 0); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + g_assert_cmphex(tim_read(td, WTCR), ==, WTCLK(1)); +} + +/* Verifies that CRST wins if both CEN and CRST are set. */ +static void test_reset_overrides_enable(gconstpointer test_data) +{ + const TestData *td = test_data; + + tim_reset(td); + + /* CRST should force CEN to 0 */ + tim_write_tcsr(td, CEN | CRST | TCSR_DEFAULT); + + g_assert_cmphex(tim_read_tcsr(td), ==, TCSR_DEFAULT); + g_assert_cmphex(tim_read_tdr(td), ==, 0); + g_assert_cmphex(tim_read(td, TISR), ==, 0); +} + +/* Verifies the behavior when CEN is set and then cleared. */ +static void test_oneshot_enable_then_disable(gconstpointer test_data) +{ + const TestData *td = test_data; + + tim_reset(td); + + /* Enable the timer with zero initial count, then disable it again. */ + tim_write_tcsr(td, CEN | TCSR_DEFAULT); + tim_write_tcsr(td, TCSR_DEFAULT); + + g_assert_cmphex(tim_read_tcsr(td), ==, TCSR_DEFAULT); + g_assert_cmphex(tim_read_tdr(td), ==, 0); + /* Timer interrupt flag should be set, but interrupts are not enabled. */ + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); +} + +/* Verifies that a one-shot timer fires when expected with prescaler 5. */ +static void test_oneshot_ps5(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = 256; + unsigned int ps = 5; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, CEN | PRESCALE(ps)); + g_assert_cmphex(tim_read_tcsr(td), ==, CEN | CACT | PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count); + + clock_step(tim_calculate_step(count, ps) - 1); + + g_assert_cmphex(tim_read_tcsr(td), ==, CEN | CACT | PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), <, count); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + + clock_step(1); + + g_assert_cmphex(tim_read_tcsr(td), ==, PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count); + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); + + /* Clear the interrupt flag. */ + tim_write(td, TISR, tim_timer_bit(td)); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); + + /* Verify that this isn't a periodic timer. */ + clock_step(2 * tim_calculate_step(count, ps)); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); +} + +/* Verifies that a one-shot timer fires when expected with prescaler 0. */ +static void test_oneshot_ps0(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = 1; + unsigned int ps = 0; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, CEN | PRESCALE(ps)); + g_assert_cmphex(tim_read_tcsr(td), ==, CEN | CACT | PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count); + + clock_step(tim_calculate_step(count, ps) - 1); + + g_assert_cmphex(tim_read_tcsr(td), ==, CEN | CACT | PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), <, count); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + + clock_step(1); + + g_assert_cmphex(tim_read_tcsr(td), ==, PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count); + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); +} + +/* Verifies that a one-shot timer fires when expected with highest prescaler. */ +static void test_oneshot_ps255(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = (1U << 24) - 1; + unsigned int ps = 255; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, CEN | PRESCALE(ps)); + g_assert_cmphex(tim_read_tcsr(td), ==, CEN | CACT | PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count); + + clock_step(tim_calculate_step(count, ps) - 1); + + g_assert_cmphex(tim_read_tcsr(td), ==, CEN | CACT | PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), <, count); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + + clock_step(1); + + g_assert_cmphex(tim_read_tcsr(td), ==, PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count); + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); +} + +/* Verifies that a oneshot timer fires an interrupt when expected. */ +static void test_oneshot_interrupt(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = 256; + unsigned int ps = 7; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, IE | CEN | MODE_ONESHOT | PRESCALE(ps)); + + clock_step_next(); + + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); + g_assert_true(qtest_get_irq(global_qtest, tim_timer_irq(td))); +} + +/* + * Verifies that the timer can be paused and later resumed, and it still fires + * at the right moment. + */ +static void test_pause_resume(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = 256; + unsigned int ps = 1; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, IE | CEN | MODE_ONESHOT | PRESCALE(ps)); + + /* Pause the timer halfway to expiration. */ + clock_step(tim_calculate_step(count / 2, ps)); + tim_write_tcsr(td, IE | MODE_ONESHOT | PRESCALE(ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count / 2); + + /* Counter should not advance during the following step. */ + clock_step(2 * tim_calculate_step(count, ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count / 2); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); + + /* Resume the timer and run _almost_ to expiration. */ + tim_write_tcsr(td, IE | CEN | MODE_ONESHOT | PRESCALE(ps)); + clock_step(tim_calculate_step(count / 2, ps) - 1); + g_assert_cmpuint(tim_read_tdr(td), <, count); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); + + /* Now, run the rest of the way and verify that the interrupt fires. */ + clock_step(1); + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); + g_assert_true(qtest_get_irq(global_qtest, tim_timer_irq(td))); +} + +/* Verifies that the prescaler can be changed while the timer is runnin. */ +static void test_prescaler_change(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = 256; + unsigned int ps = 5; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, CEN | MODE_ONESHOT | PRESCALE(ps)); + + /* Run a quarter of the way, and change the prescaler. */ + clock_step(tim_calculate_step(count / 4, ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, 3 * count / 4); + ps = 2; + tim_write_tcsr(td, CEN | MODE_ONESHOT | PRESCALE(ps)); + /* The counter must not change. */ + g_assert_cmpuint(tim_read_tdr(td), ==, 3 * count / 4); + + /* Run another quarter of the way, and change the prescaler again. */ + clock_step(tim_calculate_step(count / 4, ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count / 2); + ps = 8; + tim_write_tcsr(td, CEN | MODE_ONESHOT | PRESCALE(ps)); + /* The counter must not change. */ + g_assert_cmpuint(tim_read_tdr(td), ==, count / 2); + + /* Run another quarter of the way, and change the prescaler again. */ + clock_step(tim_calculate_step(count / 4, ps)); + g_assert_cmpuint(tim_read_tdr(td), ==, count / 4); + ps = 0; + tim_write_tcsr(td, CEN | MODE_ONESHOT | PRESCALE(ps)); + /* The counter must not change. */ + g_assert_cmpuint(tim_read_tdr(td), ==, count / 4); + + /* Run almost to expiration, and verify the timer didn't fire yet. */ + clock_step(tim_calculate_step(count / 4, ps) - 1); + g_assert_cmpuint(tim_read_tdr(td), <, count); + g_assert_cmphex(tim_read(td, TISR), ==, 0); + + /* Now, run the rest of the way and verify that the timer fires. */ + clock_step(1); + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); +} + +/* Verifies that a periodic timer automatically restarts after expiration. */ +static void test_periodic_no_interrupt(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = 2; + unsigned int ps = 3; + int i; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, CEN | MODE_PERIODIC | PRESCALE(ps)); + + for (i = 0; i < 4; i++) { + clock_step_next(); + + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); + + tim_write(td, TISR, tim_timer_bit(td)); + + g_assert_cmphex(tim_read(td, TISR), ==, 0); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); + } +} + +/* Verifies that a periodict timer fires an interrupt every time it expires. */ +static void test_periodic_interrupt(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = 65535; + unsigned int ps = 2; + int i; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, CEN | IE | MODE_PERIODIC | PRESCALE(ps)); + + for (i = 0; i < 4; i++) { + clock_step_next(); + + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); + g_assert_true(qtest_get_irq(global_qtest, tim_timer_irq(td))); + + tim_write(td, TISR, tim_timer_bit(td)); + + g_assert_cmphex(tim_read(td, TISR), ==, 0); + g_assert_false(qtest_get_irq(global_qtest, tim_timer_irq(td))); + } +} + +/* + * Verifies that the timer behaves correctly when disabled right before and + * exactly when it's supposed to expire. + */ +static void test_disable_on_expiration(gconstpointer test_data) +{ + const TestData *td = test_data; + unsigned int count = 8; + unsigned int ps = 255; + + tim_reset(td); + + tim_write_ticr(td, count); + tim_write_tcsr(td, CEN | MODE_ONESHOT | PRESCALE(ps)); + + clock_step(tim_calculate_step(count, ps) - 1); + + tim_write_tcsr(td, MODE_ONESHOT | PRESCALE(ps)); + tim_write_tcsr(td, CEN | MODE_ONESHOT | PRESCALE(ps)); + clock_step(1); + tim_write_tcsr(td, MODE_ONESHOT | PRESCALE(ps)); + g_assert_cmphex(tim_read(td, TISR), ==, tim_timer_bit(td)); +} + +/* + * Constructs a name that includes the timer block, timer and testcase name, + * and adds the test to the test suite. + */ +static void tim_add_test(const char *name, const TestData *td, GTestDataFunc fn) +{ + g_autofree char *full_name; + + full_name = g_strdup_printf("npcm7xx_timer/tim[%d]/timer[%d]/%s", + tim_index(td->tim), timer_index(td->timer), + name); + qtest_add_data_func(full_name, td, fn); +} + +/* Convenience macro for adding a test with a predictable function name. */ +#define add_test(name, td) tim_add_test(#name, td, test_##name) + +int main(int argc, char **argv) +{ + TestData testdata[ARRAY_SIZE(timer_block) * ARRAY_SIZE(timer)]; + int ret; + int i, j; + + g_test_init(&argc, &argv, NULL); + g_test_set_nonfatal_assertions(); + + for (i = 0; i < ARRAY_SIZE(timer_block); i++) { + for (j = 0; j < ARRAY_SIZE(timer); j++) { + TestData *td = &testdata[i * ARRAY_SIZE(timer) + j]; + td->tim = &timer_block[i]; + td->timer = &timer[j]; + + add_test(reset, td); + add_test(reset_overrides_enable, td); + add_test(oneshot_enable_then_disable, td); + add_test(oneshot_ps5, td); + add_test(oneshot_ps0, td); + add_test(oneshot_ps255, td); + add_test(oneshot_interrupt, td); + add_test(pause_resume, td); + add_test(prescaler_change, td); + add_test(periodic_no_interrupt, td); + add_test(periodic_interrupt, td); + add_test(disable_on_expiration, td); + } + } + + qtest_start("-machine npcm750-evb"); + qtest_irq_intercept_in(global_qtest, "/machine/soc/a9mpcore/gic"); + ret = g_test_run(); + qtest_end(); + + return ret; +} diff --git a/ui/cocoa.m b/ui/cocoa.m index 0910b4a716..f32adc3074 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -35,6 +35,7 @@ #include "sysemu/cpu-throttle.h" #include "qapi/error.h" #include "qapi/qapi-commands-block.h" +#include "qapi/qapi-commands-machine.h" #include "qapi/qapi-commands-misc.h" #include "sysemu/blockdev.h" #include "qemu-version.h" diff --git a/ui/gtk.c b/ui/gtk.c index b11594d817..a752aa22be 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -33,6 +33,7 @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "qapi/qapi-commands-control.h" +#include "qapi/qapi-commands-machine.h" #include "qapi/qapi-commands-misc.h" #include "qemu/cutils.h" |