:p
atchew
Login
Another go at the v8.5-MemTag linux-user support, plus a couple more npcm7xx devices. -- PMM The following changes since commit 8ba4bca570ace1e60614a0808631a517cf5df67a: Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging (2021-02-15 17:13:57 +0000) are available in the Git repository at: https://git.linaro.org/people/pmaydell/qemu-arm.git tags/pull-target-arm-20210216 for you to fetch changes up to 64fd5bddf3b71d1b92b55382ab39768bd87ecfbd: tests/qtests: Add npcm7xx emc model test (2021-02-16 14:27:05 +0000) ---------------------------------------------------------------- target-arm queue: * Support ARMv8.5-MemTag for linux-user * ncpm7xx: Support SMBus, EMC ethernet devices * MAINTAINERS: add section for Clock framework ---------------------------------------------------------------- Doug Evans (3): hw/net: Add npcm7xx emc model hw/arm: Add npcm7xx emc model tests/qtests: Add npcm7xx emc model test Hao Wu (5): hw/i2c: Implement NPCM7XX SMBus Module Single Mode hw/arm: Add I2C sensors for NPCM750 eval board hw/arm: Add I2C sensors and EEPROM for GSJ machine hw/i2c: Add a QTest for NPCM7XX SMBus Device hw/i2c: Implement NPCM7XX SMBus Module FIFO Mode Luc Michel (1): MAINTAINERS: add myself maintainer for the clock framework Richard Henderson (31): tcg: Introduce target-specific page data for user-only linux-user: Introduce PAGE_ANON exec: Use uintptr_t for guest_base exec: Use uintptr_t in cpu_ldst.h exec: Improve types for guest_addr_valid linux-user: Check for overflow in access_ok linux-user: Tidy VERIFY_READ/VERIFY_WRITE bsd-user: Tidy VERIFY_READ/VERIFY_WRITE linux-user: Do not use guest_addr_valid for h2g_valid linux-user: Fix guest_addr_valid vs reserved_va exec: Introduce cpu_untagged_addr exec: Use cpu_untagged_addr in g2h; split out g2h_untagged linux-user: Explicitly untag memory management syscalls linux-user: Use guest_range_valid in access_ok exec: Rename guest_{addr,range}_valid to *_untagged linux-user: Use cpu_untagged_addr in access_ok; split out *_untagged linux-user: Move lock_user et al out of line linux-user: Fix types in uaccess.c linux-user: Handle tags in lock_user/unlock_user linux-user/aarch64: Implement PR_TAGGED_ADDR_ENABLE target/arm: Improve gen_top_byte_ignore target/arm: Use the proper TBI settings for linux-user linux-user/aarch64: Implement PR_MTE_TCF and PR_MTE_TAG linux-user/aarch64: Implement PROT_MTE target/arm: Split out syndrome.h from internals.h linux-user/aarch64: Pass syndrome to EXC_*_ABORT linux-user/aarch64: Signal SEGV_MTESERR for sync tag check fault linux-user/aarch64: Signal SEGV_MTEAERR for async tag check error target/arm: Add allocation tag storage for user mode target/arm: Enable MTE for user-only tests/tcg/aarch64: Add mte smoke tests docs/system/arm/nuvoton.rst | 5 +- bsd-user/qemu.h | 17 +- include/exec/cpu-all.h | 47 +- include/exec/cpu_ldst.h | 39 +- include/exec/exec-all.h | 2 +- include/hw/arm/npcm7xx.h | 4 + include/hw/i2c/npcm7xx_smbus.h | 113 ++++ include/hw/net/npcm7xx_emc.h | 286 +++++++++ linux-user/aarch64/target_signal.h | 3 + linux-user/aarch64/target_syscall.h | 13 + linux-user/qemu.h | 76 +-- linux-user/syscall_defs.h | 1 + target/arm/cpu-param.h | 3 + target/arm/cpu.h | 32 + target/arm/internals.h | 249 +------- target/arm/syndrome.h | 273 +++++++++ tests/tcg/aarch64/mte.h | 60 ++ accel/tcg/translate-all.c | 32 +- accel/tcg/user-exec.c | 51 +- bsd-user/elfload.c | 2 +- bsd-user/main.c | 8 +- bsd-user/mmap.c | 23 +- hw/arm/npcm7xx.c | 118 +++- hw/arm/npcm7xx_boards.c | 46 ++ hw/i2c/npcm7xx_smbus.c | 1099 +++++++++++++++++++++++++++++++++++ hw/net/npcm7xx_emc.c | 857 +++++++++++++++++++++++++++ linux-user/aarch64/cpu_loop.c | 38 +- linux-user/elfload.c | 18 +- linux-user/flatload.c | 2 +- linux-user/hppa/cpu_loop.c | 39 +- linux-user/i386/cpu_loop.c | 6 +- linux-user/i386/signal.c | 5 +- linux-user/main.c | 4 +- linux-user/mmap.c | 88 +-- linux-user/ppc/signal.c | 4 +- linux-user/syscall.c | 165 ++++-- linux-user/uaccess.c | 82 ++- target/arm/cpu.c | 25 +- target/arm/helper-a64.c | 4 +- target/arm/mte_helper.c | 39 +- target/arm/tlb_helper.c | 15 +- target/arm/translate-a64.c | 25 +- target/hppa/op_helper.c | 2 +- target/i386/tcg/mem_helper.c | 2 +- target/s390x/mem_helper.c | 4 +- tests/qtest/npcm7xx_emc-test.c | 862 +++++++++++++++++++++++++++ tests/qtest/npcm7xx_smbus-test.c | 495 ++++++++++++++++ tests/tcg/aarch64/mte-1.c | 28 + tests/tcg/aarch64/mte-2.c | 45 ++ tests/tcg/aarch64/mte-3.c | 51 ++ tests/tcg/aarch64/mte-4.c | 45 ++ tests/tcg/aarch64/pauth-2.c | 1 - MAINTAINERS | 11 + hw/arm/Kconfig | 1 + hw/i2c/meson.build | 1 + hw/i2c/trace-events | 12 + hw/net/meson.build | 1 + hw/net/trace-events | 17 + tests/qtest/meson.build | 2 + tests/tcg/aarch64/Makefile.target | 6 + tests/tcg/configure.sh | 4 + 61 files changed, 5052 insertions(+), 556 deletions(-) create mode 100644 include/hw/i2c/npcm7xx_smbus.h create mode 100644 include/hw/net/npcm7xx_emc.h create mode 100644 target/arm/syndrome.h create mode 100644 tests/tcg/aarch64/mte.h create mode 100644 hw/i2c/npcm7xx_smbus.c create mode 100644 hw/net/npcm7xx_emc.c create mode 100644 tests/qtest/npcm7xx_emc-test.c create mode 100644 tests/qtest/npcm7xx_smbus-test.c create mode 100644 tests/tcg/aarch64/mte-1.c create mode 100644 tests/tcg/aarch64/mte-2.c create mode 100644 tests/tcg/aarch64/mte-3.c create mode 100644 tests/tcg/aarch64/mte-4.c
From: Richard Henderson <richard.henderson@linaro.org> This data can be allocated by page_alloc_target_data() and released by page_set_flags(start, end, prot | PAGE_RESET). This data will be used to hold tag memory for AArch64 MTE. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-2-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu-all.h | 42 +++++++++++++++++++++++++++++++++------ accel/tcg/translate-all.c | 28 ++++++++++++++++++++++++++ linux-user/mmap.c | 4 +++- linux-user/syscall.c | 4 ++-- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -XXX,XX +XXX,XX @@ extern intptr_t qemu_host_page_mask; #define PAGE_EXEC 0x0004 #define PAGE_BITS (PAGE_READ | PAGE_WRITE | PAGE_EXEC) #define PAGE_VALID 0x0008 -/* original state of the write flag (used when tracking self-modifying - code */ +/* + * Original state of the write flag (used when tracking self-modifying code) + */ #define PAGE_WRITE_ORG 0x0010 -/* Invalidate the TLB entry immediately, helpful for s390x - * Low-Address-Protection. Used with PAGE_WRITE in tlb_set_page_with_attrs() */ -#define PAGE_WRITE_INV 0x0040 +/* + * Invalidate the TLB entry immediately, helpful for s390x + * Low-Address-Protection. Used with PAGE_WRITE in tlb_set_page_with_attrs() + */ +#define PAGE_WRITE_INV 0x0020 +/* For use with page_set_flags: page is being replaced; target_data cleared. */ +#define PAGE_RESET 0x0040 + #if defined(CONFIG_BSD) && defined(CONFIG_USER_ONLY) /* FIXME: Code that sets/uses this is broken and needs to go away. */ -#define PAGE_RESERVED 0x0020 +#define PAGE_RESERVED 0x0100 #endif /* Target-specific bits that will be used via page_get_flags(). */ #define PAGE_TARGET_1 0x0080 @@ -XXX,XX +XXX,XX @@ int walk_memory_regions(void *, walk_memory_regions_fn); int page_get_flags(target_ulong address); void page_set_flags(target_ulong start, target_ulong end, int flags); int page_check_range(target_ulong start, target_ulong len, int flags); + +/** + * page_alloc_target_data(address, size) + * @address: guest virtual address + * @size: size of data to allocate + * + * Allocate @size bytes of out-of-band data to associate with the + * guest page at @address. If the page is not mapped, NULL will + * be returned. If there is existing data associated with @address, + * no new memory will be allocated. + * + * The memory will be freed when the guest page is deallocated, + * e.g. with the munmap system call. + */ +void *page_alloc_target_data(target_ulong address, size_t size); + +/** + * page_get_target_data(address) + * @address: guest virtual address + * + * Return any out-of-bound memory assocated with the guest page + * at @address, as per page_alloc_target_data. + */ +void *page_get_target_data(target_ulong address); #endif CPUArchState *cpu_copy(CPUArchState *env); diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c index XXXXXXX..XXXXXXX 100644 --- a/accel/tcg/translate-all.c +++ b/accel/tcg/translate-all.c @@ -XXX,XX +XXX,XX @@ typedef struct PageDesc { unsigned int code_write_count; #else unsigned long flags; + void *target_data; #endif #ifndef CONFIG_USER_ONLY QemuSpin lock; @@ -XXX,XX +XXX,XX @@ int page_get_flags(target_ulong address) void page_set_flags(target_ulong start, target_ulong end, int flags) { target_ulong addr, len; + bool reset_target_data; /* This function should never be called with addresses outside the guest address space. If this assert fires, it probably indicates @@ -XXX,XX +XXX,XX @@ void page_set_flags(target_ulong start, target_ulong end, int flags) if (flags & PAGE_WRITE) { flags |= PAGE_WRITE_ORG; } + reset_target_data = !(flags & PAGE_VALID) || (flags & PAGE_RESET); + flags &= ~PAGE_RESET; for (addr = start, len = end - start; len != 0; @@ -XXX,XX +XXX,XX @@ void page_set_flags(target_ulong start, target_ulong end, int flags) p->first_tb) { tb_invalidate_phys_page(addr, 0); } + if (reset_target_data && p->target_data) { + g_free(p->target_data); + p->target_data = NULL; + } p->flags = flags; } } +void *page_get_target_data(target_ulong address) +{ + PageDesc *p = page_find(address >> TARGET_PAGE_BITS); + return p ? p->target_data : NULL; +} + +void *page_alloc_target_data(target_ulong address, size_t size) +{ + PageDesc *p = page_find(address >> TARGET_PAGE_BITS); + void *ret = NULL; + + if (p->flags & PAGE_VALID) { + ret = p->target_data; + if (!ret) { + p->target_data = ret = g_malloc0(size); + } + } + return ret; +} + int page_check_range(target_ulong start, target_ulong len, int flags) { PageDesc *p; diff --git a/linux-user/mmap.c b/linux-user/mmap.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, } } the_end1: + page_flags |= PAGE_RESET; page_set_flags(start, start + len, page_flags); the_end: trace_target_mmap_complete(start); @@ -XXX,XX +XXX,XX @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, new_addr = h2g(host_addr); prot = page_get_flags(old_addr); page_set_flags(old_addr, old_addr + old_size, 0); - page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID); + page_set_flags(new_addr, new_addr + new_size, + prot | PAGE_VALID | PAGE_RESET); } tb_invalidate_phys_range(new_addr, new_addr + new_size); mmap_unlock(); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -XXX,XX +XXX,XX @@ static inline abi_ulong do_shmat(CPUArchState *cpu_env, raddr=h2g((unsigned long)host_raddr); page_set_flags(raddr, raddr + shm_info.shm_segsz, - PAGE_VALID | PAGE_READ | - ((shmflg & SHM_RDONLY)? 0 : PAGE_WRITE)); + PAGE_VALID | PAGE_RESET | PAGE_READ | + (shmflg & SHM_RDONLY ? 0 : PAGE_WRITE)); for (i = 0; i < N_SHM_REGIONS; i++) { if (!shm_regions[i].in_use) { -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Record whether the backing page is anonymous, or if it has file backing. This will allow us to get close to the Linux AArch64 ABI for MTE, which allows tag memory only on ram-backed VMAs. The real ABI allows tag memory on files, when those files are on ram-backed filesystems, such as tmpfs. We will not be able to implement that in QEMU linux-user. Thankfully, anonymous memory for malloc arenas is the primary consumer of this feature, so this restricted version should still be of use. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-3-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu-all.h | 2 ++ linux-user/mmap.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -XXX,XX +XXX,XX @@ extern intptr_t qemu_host_page_mask; #define PAGE_WRITE_INV 0x0020 /* For use with page_set_flags: page is being replaced; target_data cleared. */ #define PAGE_RESET 0x0040 +/* For linux-user, indicates that the page is MAP_ANON. */ +#define PAGE_ANON 0x0080 #if defined(CONFIG_BSD) && defined(CONFIG_USER_ONLY) /* FIXME: Code that sets/uses this is broken and needs to go away. */ diff --git a/linux-user/mmap.c b/linux-user/mmap.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, } } the_end1: + if (flags & MAP_ANONYMOUS) { + page_flags |= PAGE_ANON; + } page_flags |= PAGE_RESET; page_set_flags(start, start + len, page_flags); the_end: -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> This is more descriptive than 'unsigned long'. No functional change, since these match on all linux+bsd hosts. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-id: 20210212184902.1251044-4-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu-all.h | 2 +- bsd-user/main.c | 4 ++-- linux-user/elfload.c | 4 ++-- linux-user/main.c | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -XXX,XX +XXX,XX @@ static inline void tswap64s(uint64_t *s) /* On some host systems the guest address space is reserved on the host. * This allows the guest address space to be offset to a convenient location. */ -extern unsigned long guest_base; +extern uintptr_t guest_base; extern bool have_guest_base; extern unsigned long reserved_va; diff --git a/bsd-user/main.c b/bsd-user/main.c index XXXXXXX..XXXXXXX 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -XXX,XX +XXX,XX @@ int singlestep; unsigned long mmap_min_addr; -unsigned long guest_base; +uintptr_t guest_base; bool have_guest_base; unsigned long reserved_va; @@ -XXX,XX +XXX,XX @@ int main(int argc, char **argv) g_free(target_environ); if (qemu_loglevel_mask(CPU_LOG_PAGE)) { - qemu_log("guest_base 0x%lx\n", guest_base); + qemu_log("guest_base %p\n", (void *)guest_base); log_page_dump("binary load"); qemu_log("start_brk 0x" TARGET_ABI_FMT_lx "\n", info->start_brk); diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void pgb_have_guest_base(const char *image_name, abi_ulong guest_loaddr, void *addr, *test; if (!QEMU_IS_ALIGNED(guest_base, align)) { - fprintf(stderr, "Requested guest base 0x%lx does not satisfy " + fprintf(stderr, "Requested guest base %p does not satisfy " "host minimum alignment (0x%lx)\n", - guest_base, align); + (void *)guest_base, align); exit(EXIT_FAILURE); } diff --git a/linux-user/main.c b/linux-user/main.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -XXX,XX +XXX,XX @@ static const char *cpu_model; static const char *cpu_type; static const char *seed_optarg; unsigned long mmap_min_addr; -unsigned long guest_base; +uintptr_t guest_base; bool have_guest_base; /* @@ -XXX,XX +XXX,XX @@ int main(int argc, char **argv, char **envp) g_free(target_environ); if (qemu_loglevel_mask(CPU_LOG_PAGE)) { - qemu_log("guest_base 0x%lx\n", guest_base); + qemu_log("guest_base %p\n", (void *)guest_base); log_page_dump("binary load"); qemu_log("start_brk 0x" TARGET_ABI_FMT_lx "\n", info->start_brk); -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> This is more descriptive than 'unsigned long'. No functional change, since these match on all linux+bsd hosts. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-id: 20210212184902.1251044-5-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu_ldst.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu_ldst.h +++ b/include/exec/cpu_ldst.h @@ -XXX,XX +XXX,XX @@ typedef uint64_t abi_ptr; #endif /* All direct uses of g2h and h2g need to go away for usermode softmmu. */ -#define g2h(x) ((void *)((unsigned long)(abi_ptr)(x) + guest_base)) +#define g2h(x) ((void *)((uintptr_t)(abi_ptr)(x) + guest_base)) #if HOST_LONG_BITS <= TARGET_VIRT_ADDR_SPACE_BITS #define guest_addr_valid(x) (1) #else #define guest_addr_valid(x) ((x) <= GUEST_ADDR_MAX) #endif -#define h2g_valid(x) guest_addr_valid((unsigned long)(x) - guest_base) +#define h2g_valid(x) guest_addr_valid((uintptr_t)(x) - guest_base) static inline int guest_range_valid(unsigned long start, unsigned long len) { @@ -XXX,XX +XXX,XX @@ static inline int guest_range_valid(unsigned long start, unsigned long len) } #define h2g_nocheck(x) ({ \ - unsigned long __ret = (unsigned long)(x) - guest_base; \ + uintptr_t __ret = (uintptr_t)(x) - guest_base; \ (abi_ptr)__ret; \ }) -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Return bool not int; pass abi_ulong not 'unsigned long'. All callers use abi_ulong already, so the change in type has no effect. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-id: 20210212184902.1251044-6-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu_ldst.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu_ldst.h +++ b/include/exec/cpu_ldst.h @@ -XXX,XX +XXX,XX @@ typedef uint64_t abi_ptr; #endif #define h2g_valid(x) guest_addr_valid((uintptr_t)(x) - guest_base) -static inline int guest_range_valid(unsigned long start, unsigned long len) +static inline bool guest_range_valid(abi_ulong start, abi_ulong len) { return len - 1 <= GUEST_ADDR_MAX && start <= GUEST_ADDR_MAX - len + 1; } -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Verify that addr + size - 1 does not wrap around. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-7-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/qemu.h | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ extern unsigned long guest_stack_size; #define VERIFY_READ 0 #define VERIFY_WRITE 1 /* implies read access */ -static inline int access_ok(int type, abi_ulong addr, abi_ulong size) +static inline bool access_ok(int type, abi_ulong addr, abi_ulong size) { - return guest_addr_valid(addr) && - (size == 0 || guest_addr_valid(addr + size - 1)) && - page_check_range((target_ulong)addr, size, - (type == VERIFY_READ) ? PAGE_READ : (PAGE_READ | PAGE_WRITE)) == 0; + if (!guest_addr_valid(addr)) { + return false; + } + if (size != 0 && + (addr + size - 1 < addr || + !guest_addr_valid(addr + size - 1))) { + return false; + } + return page_check_range((target_ulong)addr, size, + (type == VERIFY_READ) ? PAGE_READ : + (PAGE_READ | PAGE_WRITE)) == 0; } /* NOTE __get_user and __put_user use host pointers and don't check access. -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> These constants are only ever used with access_ok, and friends. Rather than translating them to PAGE_* bits, let them equal the PAGE_* bits to begin. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-8-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/qemu.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ extern unsigned long guest_stack_size; /* user access */ -#define VERIFY_READ 0 -#define VERIFY_WRITE 1 /* implies read access */ +#define VERIFY_READ PAGE_READ +#define VERIFY_WRITE (PAGE_READ | PAGE_WRITE) static inline bool access_ok(int type, abi_ulong addr, abi_ulong size) { @@ -XXX,XX +XXX,XX @@ static inline bool access_ok(int type, abi_ulong addr, abi_ulong size) !guest_addr_valid(addr + size - 1))) { return false; } - return page_check_range((target_ulong)addr, size, - (type == VERIFY_READ) ? PAGE_READ : - (PAGE_READ | PAGE_WRITE)) == 0; + return page_check_range((target_ulong)addr, size, type) == 0; } /* NOTE __get_user and __put_user use host pointers and don't check access. -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> These constants are only ever used with access_ok, and friends. Rather than translating them to PAGE_* bits, let them equal the PAGE_* bits to begin. Reviewed-by: Warner Losh <imp@bsdimp.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-9-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- bsd-user/qemu.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/bsd-user/qemu.h +++ b/bsd-user/qemu.h @@ -XXX,XX +XXX,XX @@ extern unsigned long x86_stack_size; /* user access */ -#define VERIFY_READ 0 -#define VERIFY_WRITE 1 /* implies read access */ +#define VERIFY_READ PAGE_READ +#define VERIFY_WRITE (PAGE_READ | PAGE_WRITE) -static inline int access_ok(int type, abi_ulong addr, abi_ulong size) +static inline bool access_ok(int type, abi_ulong addr, abi_ulong size) { - return page_check_range((target_ulong)addr, size, - (type == VERIFY_READ) ? PAGE_READ : (PAGE_READ | PAGE_WRITE)) == 0; + return page_check_range((target_ulong)addr, size, type) == 0; } /* NOTE __get_user and __put_user use host pointers and don't check access. */ -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> This is the only use of guest_addr_valid that does not begin with a guest address, but a host address being transformed to a guest address. We will shortly adjust guest_addr_valid to handle guest memory tags, and the host address should not be subjected to that. Move h2g_valid adjacent to the other h2g macros. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-10-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu_ldst.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu_ldst.h +++ b/include/exec/cpu_ldst.h @@ -XXX,XX +XXX,XX @@ typedef uint64_t abi_ptr; #else #define guest_addr_valid(x) ((x) <= GUEST_ADDR_MAX) #endif -#define h2g_valid(x) guest_addr_valid((uintptr_t)(x) - guest_base) static inline bool guest_range_valid(abi_ulong start, abi_ulong len) { return len - 1 <= GUEST_ADDR_MAX && start <= GUEST_ADDR_MAX - len + 1; } +#define h2g_valid(x) \ + (HOST_LONG_BITS <= TARGET_VIRT_ADDR_SPACE_BITS || \ + (uintptr_t)(x) - guest_base <= GUEST_ADDR_MAX) + #define h2g_nocheck(x) ({ \ uintptr_t __ret = (uintptr_t)(x) - guest_base; \ (abi_ptr)__ret; \ -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> We must always use GUEST_ADDR_MAX, because even 32-bit hosts can use -R <reserved_va> to restrict the memory address of the guest. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-11-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu_ldst.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu_ldst.h +++ b/include/exec/cpu_ldst.h @@ -XXX,XX +XXX,XX @@ typedef uint64_t abi_ptr; /* All direct uses of g2h and h2g need to go away for usermode softmmu. */ #define g2h(x) ((void *)((uintptr_t)(abi_ptr)(x) + guest_base)) -#if HOST_LONG_BITS <= TARGET_VIRT_ADDR_SPACE_BITS -#define guest_addr_valid(x) (1) -#else -#define guest_addr_valid(x) ((x) <= GUEST_ADDR_MAX) -#endif +static inline bool guest_addr_valid(abi_ulong x) +{ + return x <= GUEST_ADDR_MAX; +} static inline bool guest_range_valid(abi_ulong start, abi_ulong len) { -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Provide an identity fallback for target that do not use tagged addresses. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-12-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu_ldst.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu_ldst.h +++ b/include/exec/cpu_ldst.h @@ -XXX,XX +XXX,XX @@ typedef uint64_t abi_ptr; #define TARGET_ABI_FMT_ptr "%"PRIx64 #endif +#ifndef TARGET_TAGGED_ADDRESSES +static inline abi_ptr cpu_untagged_addr(CPUState *cs, abi_ptr x) +{ + return x; +} +#endif + /* All direct uses of g2h and h2g need to go away for usermode softmmu. */ #define g2h(x) ((void *)((uintptr_t)(abi_ptr)(x) + guest_base)) -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Use g2h_untagged in contexts that have no cpu, e.g. the binary loaders that operate before the primary cpu is created. As a colollary, target_mmap and friends must use untagged addresses, since they are used by the loaders. Use g2h_untagged on values returned from target_mmap, as the kernel never applies a tag itself. Use g2h_untagged on all pc values. The only current user of tags, aarch64, removes tags from code addresses upon branch, so "pc" is always untagged. Use g2h with the cpu context on hand wherever possible. Use g2h_untagged in lock_user, which will be updated soon. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-13-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- bsd-user/qemu.h | 8 ++-- include/exec/cpu_ldst.h | 12 +++++- include/exec/exec-all.h | 2 +- linux-user/qemu.h | 6 +-- accel/tcg/translate-all.c | 4 +- accel/tcg/user-exec.c | 48 ++++++++++++------------ bsd-user/elfload.c | 2 +- bsd-user/main.c | 4 +- bsd-user/mmap.c | 23 ++++++------ linux-user/elfload.c | 12 +++--- linux-user/flatload.c | 2 +- linux-user/hppa/cpu_loop.c | 31 ++++++++-------- linux-user/i386/cpu_loop.c | 4 +- linux-user/mmap.c | 45 +++++++++++----------- linux-user/ppc/signal.c | 4 +- linux-user/syscall.c | 72 +++++++++++++++++++----------------- target/arm/helper-a64.c | 4 +- target/hppa/op_helper.c | 2 +- target/i386/tcg/mem_helper.c | 2 +- target/s390x/mem_helper.c | 4 +- 20 files changed, 154 insertions(+), 137 deletions(-) diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/bsd-user/qemu.h +++ b/bsd-user/qemu.h @@ -XXX,XX +XXX,XX @@ static inline void *lock_user(int type, abi_ulong guest_addr, long len, int copy void *addr; addr = g_malloc(len); if (copy) - memcpy(addr, g2h(guest_addr), len); + memcpy(addr, g2h_untagged(guest_addr), len); else memset(addr, 0, len); return addr; } #else - return g2h(guest_addr); + return g2h_untagged(guest_addr); #endif } @@ -XXX,XX +XXX,XX @@ static inline void unlock_user(void *host_ptr, abi_ulong guest_addr, #ifdef DEBUG_REMAP if (!host_ptr) return; - if (host_ptr == g2h(guest_addr)) + if (host_ptr == g2h_untagged(guest_addr)) return; if (len > 0) - memcpy(g2h(guest_addr), host_ptr, len); + memcpy(g2h_untagged(guest_addr), host_ptr, len); g_free(host_ptr); #endif } diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu_ldst.h +++ b/include/exec/cpu_ldst.h @@ -XXX,XX +XXX,XX @@ static inline abi_ptr cpu_untagged_addr(CPUState *cs, abi_ptr x) #endif /* All direct uses of g2h and h2g need to go away for usermode softmmu. */ -#define g2h(x) ((void *)((uintptr_t)(abi_ptr)(x) + guest_base)) +static inline void *g2h_untagged(abi_ptr x) +{ + return (void *)((uintptr_t)(x) + guest_base); +} + +static inline void *g2h(CPUState *cs, abi_ptr x) +{ + return g2h_untagged(cpu_untagged_addr(cs, x)); +} static inline bool guest_addr_valid(abi_ulong x) { @@ -XXX,XX +XXX,XX @@ static inline int cpu_ldsw_code(CPUArchState *env, abi_ptr addr) static inline void *tlb_vaddr_to_host(CPUArchState *env, abi_ptr addr, MMUAccessType access_type, int mmu_idx) { - return g2h(addr); + return g2h(env_cpu(env), addr); } #else void *tlb_vaddr_to_host(CPUArchState *env, abi_ptr addr, diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/exec-all.h +++ b/include/exec/exec-all.h @@ -XXX,XX +XXX,XX @@ static inline tb_page_addr_t get_page_addr_code_hostp(CPUArchState *env, void **hostp) { if (hostp) { - *hostp = g2h(addr); + *hostp = g2h_untagged(addr); } return addr; } diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ static inline void *lock_user(int type, abi_ulong guest_addr, long len, int copy return addr; } #else - return g2h(guest_addr); + return g2h_untagged(guest_addr); #endif } @@ -XXX,XX +XXX,XX @@ static inline void unlock_user(void *host_ptr, abi_ulong guest_addr, #ifdef DEBUG_REMAP if (!host_ptr) return; - if (host_ptr == g2h(guest_addr)) + if (host_ptr == g2h_untagged(guest_addr)) return; if (len > 0) - memcpy(g2h(guest_addr), host_ptr, len); + memcpy(g2h_untagged(guest_addr), host_ptr, len); g_free(host_ptr); #endif } diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c index XXXXXXX..XXXXXXX 100644 --- a/accel/tcg/translate-all.c +++ b/accel/tcg/translate-all.c @@ -XXX,XX +XXX,XX @@ static inline void tb_page_add(PageDesc *p, TranslationBlock *tb, prot |= p2->flags; p2->flags &= ~PAGE_WRITE; } - mprotect(g2h(page_addr), qemu_host_page_size, + mprotect(g2h_untagged(page_addr), qemu_host_page_size, (prot & PAGE_BITS) & ~PAGE_WRITE); if (DEBUG_TB_INVALIDATE_GATE) { printf("protecting code page: 0x" TB_PAGE_ADDR_FMT "\n", page_addr); @@ -XXX,XX +XXX,XX @@ int page_unprotect(target_ulong address, uintptr_t pc) } #endif } - mprotect((void *)g2h(host_start), qemu_host_page_size, + mprotect((void *)g2h_untagged(host_start), qemu_host_page_size, prot & PAGE_BITS); } mmap_unlock(); diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index XXXXXXX..XXXXXXX 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -XXX,XX +XXX,XX @@ int probe_access_flags(CPUArchState *env, target_ulong addr, int flags; flags = probe_access_internal(env, addr, 0, access_type, nonfault, ra); - *phost = flags ? NULL : g2h(addr); + *phost = flags ? NULL : g2h(env_cpu(env), addr); return flags; } @@ -XXX,XX +XXX,XX @@ void *probe_access(CPUArchState *env, target_ulong addr, int size, flags = probe_access_internal(env, addr, size, access_type, false, ra); g_assert(flags == 0); - return size ? g2h(addr) : NULL; + return size ? g2h(env_cpu(env), addr) : NULL; } #if defined(__i386__) @@ -XXX,XX +XXX,XX @@ uint32_t cpu_ldub_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_UB, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = ldub_p(g2h(ptr)); + ret = ldub_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ int cpu_ldsb_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_SB, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = ldsb_p(g2h(ptr)); + ret = ldsb_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ uint32_t cpu_lduw_be_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_BEUW, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = lduw_be_p(g2h(ptr)); + ret = lduw_be_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ int cpu_ldsw_be_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_BESW, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = ldsw_be_p(g2h(ptr)); + ret = ldsw_be_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ uint32_t cpu_ldl_be_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_BEUL, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = ldl_be_p(g2h(ptr)); + ret = ldl_be_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ uint64_t cpu_ldq_be_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_BEQ, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = ldq_be_p(g2h(ptr)); + ret = ldq_be_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ uint32_t cpu_lduw_le_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_LEUW, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = lduw_le_p(g2h(ptr)); + ret = lduw_le_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ int cpu_ldsw_le_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_LESW, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = ldsw_le_p(g2h(ptr)); + ret = ldsw_le_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ uint32_t cpu_ldl_le_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_LEUL, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = ldl_le_p(g2h(ptr)); + ret = ldl_le_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ uint64_t cpu_ldq_le_data(CPUArchState *env, abi_ptr ptr) uint16_t meminfo = trace_mem_get_info(MO_LEQ, MMU_USER_IDX, false); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - ret = ldq_le_p(g2h(ptr)); + ret = ldq_le_p(g2h(env_cpu(env), ptr)); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); return ret; } @@ -XXX,XX +XXX,XX @@ void cpu_stb_data(CPUArchState *env, abi_ptr ptr, uint32_t val) uint16_t meminfo = trace_mem_get_info(MO_UB, MMU_USER_IDX, true); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - stb_p(g2h(ptr), val); + stb_p(g2h(env_cpu(env), ptr), val); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); } @@ -XXX,XX +XXX,XX @@ void cpu_stw_be_data(CPUArchState *env, abi_ptr ptr, uint32_t val) uint16_t meminfo = trace_mem_get_info(MO_BEUW, MMU_USER_IDX, true); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - stw_be_p(g2h(ptr), val); + stw_be_p(g2h(env_cpu(env), ptr), val); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); } @@ -XXX,XX +XXX,XX @@ void cpu_stl_be_data(CPUArchState *env, abi_ptr ptr, uint32_t val) uint16_t meminfo = trace_mem_get_info(MO_BEUL, MMU_USER_IDX, true); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - stl_be_p(g2h(ptr), val); + stl_be_p(g2h(env_cpu(env), ptr), val); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); } @@ -XXX,XX +XXX,XX @@ void cpu_stq_be_data(CPUArchState *env, abi_ptr ptr, uint64_t val) uint16_t meminfo = trace_mem_get_info(MO_BEQ, MMU_USER_IDX, true); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - stq_be_p(g2h(ptr), val); + stq_be_p(g2h(env_cpu(env), ptr), val); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); } @@ -XXX,XX +XXX,XX @@ void cpu_stw_le_data(CPUArchState *env, abi_ptr ptr, uint32_t val) uint16_t meminfo = trace_mem_get_info(MO_LEUW, MMU_USER_IDX, true); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - stw_le_p(g2h(ptr), val); + stw_le_p(g2h(env_cpu(env), ptr), val); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); } @@ -XXX,XX +XXX,XX @@ void cpu_stl_le_data(CPUArchState *env, abi_ptr ptr, uint32_t val) uint16_t meminfo = trace_mem_get_info(MO_LEUL, MMU_USER_IDX, true); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - stl_le_p(g2h(ptr), val); + stl_le_p(g2h(env_cpu(env), ptr), val); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); } @@ -XXX,XX +XXX,XX @@ void cpu_stq_le_data(CPUArchState *env, abi_ptr ptr, uint64_t val) uint16_t meminfo = trace_mem_get_info(MO_LEQ, MMU_USER_IDX, true); trace_guest_mem_before_exec(env_cpu(env), ptr, meminfo); - stq_le_p(g2h(ptr), val); + stq_le_p(g2h(env_cpu(env), ptr), val); qemu_plugin_vcpu_mem_cb(env_cpu(env), ptr, meminfo); } @@ -XXX,XX +XXX,XX @@ uint32_t cpu_ldub_code(CPUArchState *env, abi_ptr ptr) uint32_t ret; set_helper_retaddr(1); - ret = ldub_p(g2h(ptr)); + ret = ldub_p(g2h_untagged(ptr)); clear_helper_retaddr(); return ret; } @@ -XXX,XX +XXX,XX @@ uint32_t cpu_lduw_code(CPUArchState *env, abi_ptr ptr) uint32_t ret; set_helper_retaddr(1); - ret = lduw_p(g2h(ptr)); + ret = lduw_p(g2h_untagged(ptr)); clear_helper_retaddr(); return ret; } @@ -XXX,XX +XXX,XX @@ uint32_t cpu_ldl_code(CPUArchState *env, abi_ptr ptr) uint32_t ret; set_helper_retaddr(1); - ret = ldl_p(g2h(ptr)); + ret = ldl_p(g2h_untagged(ptr)); clear_helper_retaddr(); return ret; } @@ -XXX,XX +XXX,XX @@ uint64_t cpu_ldq_code(CPUArchState *env, abi_ptr ptr) uint64_t ret; set_helper_retaddr(1); - ret = ldq_p(g2h(ptr)); + ret = ldq_p(g2h_untagged(ptr)); clear_helper_retaddr(); return ret; } @@ -XXX,XX +XXX,XX @@ static void *atomic_mmu_lookup(CPUArchState *env, target_ulong addr, if (unlikely(addr & (size - 1))) { cpu_loop_exit_atomic(env_cpu(env), retaddr); } - void *ret = g2h(addr); + void *ret = g2h(env_cpu(env), addr); set_helper_retaddr(retaddr); return ret; } diff --git a/bsd-user/elfload.c b/bsd-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/bsd-user/elfload.c +++ b/bsd-user/elfload.c @@ -XXX,XX +XXX,XX @@ static void padzero(abi_ulong elf_bss, abi_ulong last_bss) end_addr1 = REAL_HOST_PAGE_ALIGN(elf_bss); end_addr = HOST_PAGE_ALIGN(elf_bss); if (end_addr1 < end_addr) { - mmap((void *)g2h(end_addr1), end_addr - end_addr1, + mmap((void *)g2h_untagged(end_addr1), end_addr - end_addr1, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0); } diff --git a/bsd-user/main.c b/bsd-user/main.c index XXXXXXX..XXXXXXX 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -XXX,XX +XXX,XX @@ int main(int argc, char **argv) env->idt.base = target_mmap(0, sizeof(uint64_t) * (env->idt.limit + 1), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); - idt_table = g2h(env->idt.base); + idt_table = g2h_untagged(env->idt.base); set_idt(0, 0); set_idt(1, 0); set_idt(2, 0); @@ -XXX,XX +XXX,XX @@ int main(int argc, char **argv) PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); env->gdt.limit = sizeof(uint64_t) * TARGET_GDT_ENTRIES - 1; - gdt_table = g2h(env->gdt.base); + gdt_table = g2h_untagged(env->gdt.base); #ifdef TARGET_ABI32 write_dt(&gdt_table[__USER_CS >> 3], 0, 0xfffff, DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | diff --git a/bsd-user/mmap.c b/bsd-user/mmap.c index XXXXXXX..XXXXXXX 100644 --- a/bsd-user/mmap.c +++ b/bsd-user/mmap.c @@ -XXX,XX +XXX,XX @@ int target_mprotect(abi_ulong start, abi_ulong len, int prot) } end = host_end; } - ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS); + ret = mprotect(g2h_untagged(host_start), + qemu_host_page_size, prot1 & PAGE_BITS); if (ret != 0) goto error; host_start += qemu_host_page_size; @@ -XXX,XX +XXX,XX @@ int target_mprotect(abi_ulong start, abi_ulong len, int prot) for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { prot1 |= page_get_flags(addr); } - ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size, - prot1 & PAGE_BITS); + ret = mprotect(g2h_untagged(host_end - qemu_host_page_size), + qemu_host_page_size, prot1 & PAGE_BITS); if (ret != 0) goto error; host_end -= qemu_host_page_size; @@ -XXX,XX +XXX,XX @@ int target_mprotect(abi_ulong start, abi_ulong len, int prot) /* handle the pages in the middle */ if (host_start < host_end) { - ret = mprotect(g2h(host_start), host_end - host_start, prot); + ret = mprotect(g2h_untagged(host_start), host_end - host_start, prot); if (ret != 0) goto error; } @@ -XXX,XX +XXX,XX @@ static int mmap_frag(abi_ulong real_start, int prot1, prot_new; real_end = real_start + qemu_host_page_size; - host_start = g2h(real_start); + host_start = g2h_untagged(real_start); /* get the protection of the target pages outside the mapping */ prot1 = 0; @@ -XXX,XX +XXX,XX @@ static int mmap_frag(abi_ulong real_start, mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE); /* read the corresponding file data */ - pread(fd, g2h(start), end - start, offset); + pread(fd, g2h_untagged(start), end - start, offset); /* put final protection */ if (prot_new != (prot1 | PROT_WRITE)) @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, /* Note: we prefer to control the mapping address. It is especially important if qemu_host_page_size > qemu_real_host_page_size */ - p = mmap(g2h(mmap_start), + p = mmap(g2h_untagged(mmap_start), host_len, prot, flags | MAP_FIXED, fd, host_offset); if (p == MAP_FAILED) goto fail; @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, -1, 0); if (retaddr == -1) goto fail; - pread(fd, g2h(start), len, offset); + pread(fd, g2h_untagged(start), len, offset); if (!(prot & PROT_WRITE)) { ret = target_mprotect(start, len, prot); if (ret != 0) { @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, offset1 = 0; else offset1 = offset + real_start - start; - p = mmap(g2h(real_start), real_end - real_start, + p = mmap(g2h_untagged(real_start), real_end - real_start, prot, flags, fd, offset1); if (p == MAP_FAILED) goto fail; @@ -XXX,XX +XXX,XX @@ int target_munmap(abi_ulong start, abi_ulong len) ret = 0; /* unmap what we can */ if (real_start < real_end) { - ret = munmap(g2h(real_start), real_end - real_start); + ret = munmap(g2h_untagged(real_start), real_end - real_start); } if (ret == 0) @@ -XXX,XX +XXX,XX @@ int target_msync(abi_ulong start, abi_ulong len, int flags) return 0; start &= qemu_host_page_mask; - return msync(g2h(start), end - start, flags); + return msync(g2h_untagged(start), end - start, flags); } diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ enum { static bool init_guest_commpage(void) { - void *want = g2h(ARM_COMMPAGE & -qemu_host_page_size); + void *want = g2h_untagged(ARM_COMMPAGE & -qemu_host_page_size); void *addr = mmap(want, qemu_host_page_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0); @@ -XXX,XX +XXX,XX @@ static bool init_guest_commpage(void) } /* Set kernel helper versions; rest of page is 0. */ - __put_user(5, (uint32_t *)g2h(0xffff0ffcu)); + __put_user(5, (uint32_t *)g2h_untagged(0xffff0ffcu)); if (mprotect(addr, qemu_host_page_size, PROT_READ)) { perror("Protecting guest commpage"); @@ -XXX,XX +XXX,XX @@ static void zero_bss(abi_ulong elf_bss, abi_ulong last_bss, int prot) here is still actually needed. For now, continue with it, but merge it with the "normal" mmap that would allocate the bss. */ - host_start = (uintptr_t) g2h(elf_bss); - host_end = (uintptr_t) g2h(last_bss); + host_start = (uintptr_t) g2h_untagged(elf_bss); + host_end = (uintptr_t) g2h_untagged(last_bss); host_map_start = REAL_HOST_PAGE_ALIGN(host_start); if (host_map_start < host_end) { @@ -XXX,XX +XXX,XX @@ static void pgb_have_guest_base(const char *image_name, abi_ulong guest_loaddr, } /* Reserve the address space for the binary, or reserved_va. */ - test = g2h(guest_loaddr); + test = g2h_untagged(guest_loaddr); addr = mmap(test, guest_hiaddr - guest_loaddr, PROT_NONE, flags, -1, 0); if (test != addr) { pgb_fail_in_use(image_name); @@ -XXX,XX +XXX,XX @@ static void pgb_reserved_va(const char *image_name, abi_ulong guest_loaddr, /* Reserve the memory on the host. */ assert(guest_base != 0); - test = g2h(0); + test = g2h_untagged(0); addr = mmap(test, reserved_va, PROT_NONE, flags, -1, 0); if (addr == MAP_FAILED || addr != test) { error_report("Unable to reserve 0x%lx bytes of virtual address " diff --git a/linux-user/flatload.c b/linux-user/flatload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/flatload.c +++ b/linux-user/flatload.c @@ -XXX,XX +XXX,XX @@ static int load_flat_file(struct linux_binprm * bprm, } /* zero the BSS. */ - memset(g2h(datapos + data_len), 0, bss_len); + memset(g2h_untagged(datapos + data_len), 0, bss_len); return 0; } diff --git a/linux-user/hppa/cpu_loop.c b/linux-user/hppa/cpu_loop.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/hppa/cpu_loop.c +++ b/linux-user/hppa/cpu_loop.c @@ -XXX,XX +XXX,XX @@ static abi_ulong hppa_lws(CPUHPPAState *env) { + CPUState *cs = env_cpu(env); uint32_t which = env->gr[20]; abi_ulong addr = env->gr[26]; abi_ulong old = env->gr[25]; @@ -XXX,XX +XXX,XX @@ static abi_ulong hppa_lws(CPUHPPAState *env) } old = tswap32(old); new = tswap32(new); - ret = qatomic_cmpxchg((uint32_t *)g2h(addr), old, new); + ret = qatomic_cmpxchg((uint32_t *)g2h(cs, addr), old, new); ret = tswap32(ret); break; @@ -XXX,XX +XXX,XX @@ static abi_ulong hppa_lws(CPUHPPAState *env) can be host-endian as well. */ switch (size) { case 0: - old = *(uint8_t *)g2h(old); - new = *(uint8_t *)g2h(new); - ret = qatomic_cmpxchg((uint8_t *)g2h(addr), old, new); + old = *(uint8_t *)g2h(cs, old); + new = *(uint8_t *)g2h(cs, new); + ret = qatomic_cmpxchg((uint8_t *)g2h(cs, addr), old, new); ret = ret != old; break; case 1: - old = *(uint16_t *)g2h(old); - new = *(uint16_t *)g2h(new); - ret = qatomic_cmpxchg((uint16_t *)g2h(addr), old, new); + old = *(uint16_t *)g2h(cs, old); + new = *(uint16_t *)g2h(cs, new); + ret = qatomic_cmpxchg((uint16_t *)g2h(cs, addr), old, new); ret = ret != old; break; case 2: - old = *(uint32_t *)g2h(old); - new = *(uint32_t *)g2h(new); - ret = qatomic_cmpxchg((uint32_t *)g2h(addr), old, new); + old = *(uint32_t *)g2h(cs, old); + new = *(uint32_t *)g2h(cs, new); + ret = qatomic_cmpxchg((uint32_t *)g2h(cs, addr), old, new); ret = ret != old; break; case 3: { uint64_t o64, n64, r64; - o64 = *(uint64_t *)g2h(old); - n64 = *(uint64_t *)g2h(new); + o64 = *(uint64_t *)g2h(cs, old); + n64 = *(uint64_t *)g2h(cs, new); #ifdef CONFIG_ATOMIC64 - r64 = qatomic_cmpxchg__nocheck((uint64_t *)g2h(addr), + r64 = qatomic_cmpxchg__nocheck((uint64_t *)g2h(cs, addr), o64, n64); ret = r64 != o64; #else start_exclusive(); - r64 = *(uint64_t *)g2h(addr); + r64 = *(uint64_t *)g2h(cs, addr); ret = 1; if (r64 == o64) { - *(uint64_t *)g2h(addr) = n64; + *(uint64_t *)g2h(cs, addr) = n64; ret = 0; } end_exclusive(); diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/i386/cpu_loop.c +++ b/linux-user/i386/cpu_loop.c @@ -XXX,XX +XXX,XX @@ void target_cpu_copy_regs(CPUArchState *env, struct target_pt_regs *regs) env->idt.base = target_mmap(0, sizeof(uint64_t) * (env->idt.limit + 1), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); - idt_table = g2h(env->idt.base); + idt_table = g2h_untagged(env->idt.base); set_idt(0, 0); set_idt(1, 0); set_idt(2, 0); @@ -XXX,XX +XXX,XX @@ void target_cpu_copy_regs(CPUArchState *env, struct target_pt_regs *regs) PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); env->gdt.limit = sizeof(uint64_t) * TARGET_GDT_ENTRIES - 1; - gdt_table = g2h(env->gdt.base); + gdt_table = g2h_untagged(env->gdt.base); #ifdef TARGET_ABI32 write_dt(&gdt_table[__USER_CS >> 3], 0, 0xfffff, DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | diff --git a/linux-user/mmap.c b/linux-user/mmap.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -XXX,XX +XXX,XX @@ int target_mprotect(abi_ulong start, abi_ulong len, int target_prot) } end = host_end; } - ret = mprotect(g2h(host_start), qemu_host_page_size, + ret = mprotect(g2h_untagged(host_start), qemu_host_page_size, prot1 & PAGE_BITS); if (ret != 0) { goto error; @@ -XXX,XX +XXX,XX @@ int target_mprotect(abi_ulong start, abi_ulong len, int target_prot) for (addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { prot1 |= page_get_flags(addr); } - ret = mprotect(g2h(host_end - qemu_host_page_size), + ret = mprotect(g2h_untagged(host_end - qemu_host_page_size), qemu_host_page_size, prot1 & PAGE_BITS); if (ret != 0) { goto error; @@ -XXX,XX +XXX,XX @@ int target_mprotect(abi_ulong start, abi_ulong len, int target_prot) /* handle the pages in the middle */ if (host_start < host_end) { - ret = mprotect(g2h(host_start), host_end - host_start, host_prot); + ret = mprotect(g2h_untagged(host_start), + host_end - host_start, host_prot); if (ret != 0) { goto error; } @@ -XXX,XX +XXX,XX @@ static int mmap_frag(abi_ulong real_start, int prot1, prot_new; real_end = real_start + qemu_host_page_size; - host_start = g2h(real_start); + host_start = g2h_untagged(real_start); /* get the protection of the target pages outside the mapping */ prot1 = 0; @@ -XXX,XX +XXX,XX @@ static int mmap_frag(abi_ulong real_start, mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE); /* read the corresponding file data */ - if (pread(fd, g2h(start), end - start, offset) == -1) + if (pread(fd, g2h_untagged(start), end - start, offset) == -1) return -1; /* put final protection */ @@ -XXX,XX +XXX,XX @@ static int mmap_frag(abi_ulong real_start, mprotect(host_start, qemu_host_page_size, prot_new); } if (prot_new & PROT_WRITE) { - memset(g2h(start), 0, end - start); + memset(g2h_untagged(start), 0, end - start); } } return 0; @@ -XXX,XX +XXX,XX @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size, abi_ulong align) * - mremap() with MREMAP_FIXED flag * - shmat() with SHM_REMAP flag */ - ptr = mmap(g2h(addr), size, PROT_NONE, + ptr = mmap(g2h_untagged(addr), size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE, -1, 0); /* ENOMEM, if host address space has no memory */ @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, /* Note: we prefer to control the mapping address. It is especially important if qemu_host_page_size > qemu_real_host_page_size */ - p = mmap(g2h(start), host_len, host_prot, + p = mmap(g2h_untagged(start), host_len, host_prot, flags | MAP_FIXED | MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) { goto fail; @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, /* update start so that it points to the file position at 'offset' */ host_start = (unsigned long)p; if (!(flags & MAP_ANONYMOUS)) { - p = mmap(g2h(start), len, host_prot, + p = mmap(g2h_untagged(start), len, host_prot, flags | MAP_FIXED, fd, host_offset); if (p == MAP_FAILED) { - munmap(g2h(start), host_len); + munmap(g2h_untagged(start), host_len); goto fail; } host_start += offset - host_offset; @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, -1, 0); if (retaddr == -1) goto fail; - if (pread(fd, g2h(start), len, offset) == -1) + if (pread(fd, g2h_untagged(start), len, offset) == -1) goto fail; if (!(host_prot & PROT_WRITE)) { ret = target_mprotect(start, len, target_prot); @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, offset1 = 0; else offset1 = offset + real_start - start; - p = mmap(g2h(real_start), real_end - real_start, + p = mmap(g2h_untagged(real_start), real_end - real_start, host_prot, flags, fd, offset1); if (p == MAP_FAILED) goto fail; @@ -XXX,XX +XXX,XX @@ static void mmap_reserve(abi_ulong start, abi_ulong size) real_end -= qemu_host_page_size; } if (real_start != real_end) { - mmap(g2h(real_start), real_end - real_start, PROT_NONE, + mmap(g2h_untagged(real_start), real_end - real_start, PROT_NONE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0); } @@ -XXX,XX +XXX,XX @@ int target_munmap(abi_ulong start, abi_ulong len) if (reserved_va) { mmap_reserve(real_start, real_end - real_start); } else { - ret = munmap(g2h(real_start), real_end - real_start); + ret = munmap(g2h_untagged(real_start), real_end - real_start); } } @@ -XXX,XX +XXX,XX @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, mmap_lock(); if (flags & MREMAP_FIXED) { - host_addr = mremap(g2h(old_addr), old_size, new_size, - flags, g2h(new_addr)); + host_addr = mremap(g2h_untagged(old_addr), old_size, new_size, + flags, g2h_untagged(new_addr)); if (reserved_va && host_addr != MAP_FAILED) { /* If new and old addresses overlap then the above mremap will @@ -XXX,XX +XXX,XX @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, errno = ENOMEM; host_addr = MAP_FAILED; } else { - host_addr = mremap(g2h(old_addr), old_size, new_size, - flags | MREMAP_FIXED, g2h(mmap_start)); + host_addr = mremap(g2h_untagged(old_addr), old_size, new_size, + flags | MREMAP_FIXED, + g2h_untagged(mmap_start)); if (reserved_va) { mmap_reserve(old_addr, old_size); } @@ -XXX,XX +XXX,XX @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, } } if (prot == 0) { - host_addr = mremap(g2h(old_addr), old_size, new_size, flags); + host_addr = mremap(g2h_untagged(old_addr), + old_size, new_size, flags); if (host_addr != MAP_FAILED) { /* Check if address fits target address space */ if (!guest_range_valid(h2g(host_addr), new_size)) { /* Revert mremap() changes */ - host_addr = mremap(g2h(old_addr), new_size, old_size, - flags); + host_addr = mremap(g2h_untagged(old_addr), + new_size, old_size, flags); errno = ENOMEM; host_addr = MAP_FAILED; } else if (reserved_va && old_size > new_size) { diff --git a/linux-user/ppc/signal.c b/linux-user/ppc/signal.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/ppc/signal.c +++ b/linux-user/ppc/signal.c @@ -XXX,XX +XXX,XX @@ static void restore_user_regs(CPUPPCState *env, uint64_t v_addr; /* 64-bit needs to recover the pointer to the vectors from the frame */ __get_user(v_addr, &frame->v_regs); - v_regs = g2h(v_addr); + v_regs = g2h(env_cpu(env), v_addr); #else v_regs = (ppc_avr_t *)frame->mc_vregs.altivec; #endif @@ -XXX,XX +XXX,XX @@ void setup_rt_frame(int sig, struct target_sigaction *ka, if (get_ppc64_abi(image) < 2) { /* ELFv1 PPC64 function pointers are pointers to OPD entries. */ struct target_func_ptr *handler = - (struct target_func_ptr *)g2h(ka->_sa_handler); + (struct target_func_ptr *)g2h(env_cpu(env), ka->_sa_handler); env->nip = tswapl(handler->entry); env->gpr[2] = tswapl(handler->toc); } else { diff --git a/linux-user/syscall.c b/linux-user/syscall.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -XXX,XX +XXX,XX @@ abi_long do_brk(abi_ulong new_brk) /* Heap contents are initialized to zero, as for anonymous * mapped pages. */ if (new_brk > target_brk) { - memset(g2h(target_brk), 0, new_brk - target_brk); + memset(g2h_untagged(target_brk), 0, new_brk - target_brk); } target_brk = new_brk; DEBUGF_BRK(TARGET_ABI_FMT_lx " (new_brk <= brk_page)\n", target_brk); @@ -XXX,XX +XXX,XX @@ abi_long do_brk(abi_ulong new_brk) * come from the remaining part of the previous page: it may * contains garbage data due to a previous heap usage (grown * then shrunken). */ - memset(g2h(target_brk), 0, brk_page - target_brk); + memset(g2h_untagged(target_brk), 0, brk_page - target_brk); target_brk = new_brk; brk_page = HOST_PAGE_ALIGN(target_brk); @@ -XXX,XX +XXX,XX @@ static inline abi_ulong do_shmat(CPUArchState *cpu_env, mmap_lock(); if (shmaddr) - host_raddr = shmat(shmid, (void *)g2h(shmaddr), shmflg); + host_raddr = shmat(shmid, (void *)g2h_untagged(shmaddr), shmflg); else { abi_ulong mmap_start; @@ -XXX,XX +XXX,XX @@ static inline abi_ulong do_shmat(CPUArchState *cpu_env, errno = ENOMEM; host_raddr = (void *)-1; } else - host_raddr = shmat(shmid, g2h(mmap_start), shmflg | SHM_REMAP); + host_raddr = shmat(shmid, g2h_untagged(mmap_start), + shmflg | SHM_REMAP); } if (host_raddr == (void *)-1) { @@ -XXX,XX +XXX,XX @@ static inline abi_long do_shmdt(abi_ulong shmaddr) break; } } - rv = get_errno(shmdt(g2h(shmaddr))); + rv = get_errno(shmdt(g2h_untagged(shmaddr))); mmap_unlock(); @@ -XXX,XX +XXX,XX @@ static abi_long write_ldt(CPUX86State *env, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (env->ldt.base == -1) return -TARGET_ENOMEM; - memset(g2h(env->ldt.base), 0, + memset(g2h_untagged(env->ldt.base), 0, TARGET_LDT_ENTRIES * TARGET_LDT_ENTRY_SIZE); env->ldt.limit = 0xffff; - ldt_table = g2h(env->ldt.base); + ldt_table = g2h_untagged(env->ldt.base); } /* NOTE: same code as Linux kernel */ @@ -XXX,XX +XXX,XX @@ static abi_long do_modify_ldt(CPUX86State *env, int func, abi_ulong ptr, #if defined(TARGET_ABI32) abi_long do_set_thread_area(CPUX86State *env, abi_ulong ptr) { - uint64_t *gdt_table = g2h(env->gdt.base); + uint64_t *gdt_table = g2h_untagged(env->gdt.base); struct target_modify_ldt_ldt_s ldt_info; struct target_modify_ldt_ldt_s *target_ldt_info; int seg_32bit, contents, read_exec_only, limit_in_pages; @@ -XXX,XX +XXX,XX @@ install: static abi_long do_get_thread_area(CPUX86State *env, abi_ulong ptr) { struct target_modify_ldt_ldt_s *target_ldt_info; - uint64_t *gdt_table = g2h(env->gdt.base); + uint64_t *gdt_table = g2h_untagged(env->gdt.base); uint32_t base_addr, limit, flags; int seg_32bit, contents, read_exec_only, limit_in_pages, idx; int seg_not_present, useable, lm; @@ -XXX,XX +XXX,XX @@ static int do_safe_futex(int *uaddr, int op, int val, tricky. However they're probably useless because guest atomic operations won't work either. */ #if defined(TARGET_NR_futex) -static int do_futex(target_ulong uaddr, int op, int val, target_ulong timeout, - target_ulong uaddr2, int val3) +static int do_futex(CPUState *cpu, target_ulong uaddr, int op, int val, + target_ulong timeout, target_ulong uaddr2, int val3) { struct timespec ts, *pts; int base_op; @@ -XXX,XX +XXX,XX @@ static int do_futex(target_ulong uaddr, int op, int val, target_ulong timeout, } else { pts = NULL; } - return do_safe_futex(g2h(uaddr), op, tswap32(val), pts, NULL, val3); + return do_safe_futex(g2h(cpu, uaddr), + op, tswap32(val), pts, NULL, val3); case FUTEX_WAKE: - return do_safe_futex(g2h(uaddr), op, val, NULL, NULL, 0); + return do_safe_futex(g2h(cpu, uaddr), + op, val, NULL, NULL, 0); case FUTEX_FD: - return do_safe_futex(g2h(uaddr), op, val, NULL, NULL, 0); + return do_safe_futex(g2h(cpu, uaddr), + op, val, NULL, NULL, 0); case FUTEX_REQUEUE: case FUTEX_CMP_REQUEUE: case FUTEX_WAKE_OP: @@ -XXX,XX +XXX,XX @@ static int do_futex(target_ulong uaddr, int op, int val, target_ulong timeout, to satisfy the compiler. We do not need to tswap TIMEOUT since it's not compared to guest memory. */ pts = (struct timespec *)(uintptr_t) timeout; - return do_safe_futex(g2h(uaddr), op, val, pts, g2h(uaddr2), + return do_safe_futex(g2h(cpu, uaddr), op, val, pts, g2h(cpu, uaddr2), (base_op == FUTEX_CMP_REQUEUE - ? tswap32(val3) - : val3)); + ? tswap32(val3) : val3)); default: return -TARGET_ENOSYS; } @@ -XXX,XX +XXX,XX @@ static int do_futex(target_ulong uaddr, int op, int val, target_ulong timeout, #endif #if defined(TARGET_NR_futex_time64) -static int do_futex_time64(target_ulong uaddr, int op, int val, target_ulong timeout, +static int do_futex_time64(CPUState *cpu, target_ulong uaddr, int op, + int val, target_ulong timeout, target_ulong uaddr2, int val3) { struct timespec ts, *pts; @@ -XXX,XX +XXX,XX @@ static int do_futex_time64(target_ulong uaddr, int op, int val, target_ulong tim } else { pts = NULL; } - return do_safe_futex(g2h(uaddr), op, tswap32(val), pts, NULL, val3); + return do_safe_futex(g2h(cpu, uaddr), op, + tswap32(val), pts, NULL, val3); case FUTEX_WAKE: - return do_safe_futex(g2h(uaddr), op, val, NULL, NULL, 0); + return do_safe_futex(g2h(cpu, uaddr), op, val, NULL, NULL, 0); case FUTEX_FD: - return do_safe_futex(g2h(uaddr), op, val, NULL, NULL, 0); + return do_safe_futex(g2h(cpu, uaddr), op, val, NULL, NULL, 0); case FUTEX_REQUEUE: case FUTEX_CMP_REQUEUE: case FUTEX_WAKE_OP: @@ -XXX,XX +XXX,XX @@ static int do_futex_time64(target_ulong uaddr, int op, int val, target_ulong tim to satisfy the compiler. We do not need to tswap TIMEOUT since it's not compared to guest memory. */ pts = (struct timespec *)(uintptr_t) timeout; - return do_safe_futex(g2h(uaddr), op, val, pts, g2h(uaddr2), + return do_safe_futex(g2h(cpu, uaddr), op, val, pts, g2h(cpu, uaddr2), (base_op == FUTEX_CMP_REQUEUE - ? tswap32(val3) - : val3)); + ? tswap32(val3) : val3)); default: return -TARGET_ENOSYS; } @@ -XXX,XX +XXX,XX @@ static int open_self_maps(void *cpu_env, int fd) const char *path; max = h2g_valid(max - 1) ? - max : (uintptr_t) g2h(GUEST_ADDR_MAX) + 1; + max : (uintptr_t) g2h_untagged(GUEST_ADDR_MAX) + 1; if (page_check_range(h2g(min), max - min, flags) == -1) { continue; @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, if (ts->child_tidptr) { put_user_u32(0, ts->child_tidptr); - do_sys_futex(g2h(ts->child_tidptr), FUTEX_WAKE, INT_MAX, - NULL, NULL, 0); + do_sys_futex(g2h(cpu, ts->child_tidptr), + FUTEX_WAKE, INT_MAX, NULL, NULL, 0); } thread_cpu = NULL; g_free(ts); @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, if (!arg5) { ret = mount(p, p2, p3, (unsigned long)arg4, NULL); } else { - ret = mount(p, p2, p3, (unsigned long)arg4, g2h(arg5)); + ret = mount(p, p2, p3, (unsigned long)arg4, g2h(cpu, arg5)); } ret = get_errno(ret); @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, /* ??? msync/mlock/munlock are broken for softmmu. */ #ifdef TARGET_NR_msync case TARGET_NR_msync: - return get_errno(msync(g2h(arg1), arg2, arg3)); + return get_errno(msync(g2h(cpu, arg1), arg2, arg3)); #endif #ifdef TARGET_NR_mlock case TARGET_NR_mlock: - return get_errno(mlock(g2h(arg1), arg2)); + return get_errno(mlock(g2h(cpu, arg1), arg2)); #endif #ifdef TARGET_NR_munlock case TARGET_NR_munlock: - return get_errno(munlock(g2h(arg1), arg2)); + return get_errno(munlock(g2h(cpu, arg1), arg2)); #endif #ifdef TARGET_NR_mlockall case TARGET_NR_mlockall: @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, #if defined(TARGET_NR_set_tid_address) && defined(__NR_set_tid_address) case TARGET_NR_set_tid_address: - return get_errno(set_tid_address((int *)g2h(arg1))); + return get_errno(set_tid_address((int *)g2h(cpu, arg1))); #endif case TARGET_NR_tkill: @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, #endif #ifdef TARGET_NR_futex case TARGET_NR_futex: - return do_futex(arg1, arg2, arg3, arg4, arg5, arg6); + return do_futex(cpu, arg1, arg2, arg3, arg4, arg5, arg6); #endif #ifdef TARGET_NR_futex_time64 case TARGET_NR_futex_time64: - return do_futex_time64(arg1, arg2, arg3, arg4, arg5, arg6); + return do_futex_time64(cpu, arg1, arg2, arg3, arg4, arg5, arg6); #endif #if defined(TARGET_NR_inotify_init) && defined(__NR_inotify_init) case TARGET_NR_inotify_init: diff --git a/target/arm/helper-a64.c b/target/arm/helper-a64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/helper-a64.c +++ b/target/arm/helper-a64.c @@ -XXX,XX +XXX,XX @@ uint64_t HELPER(paired_cmpxchg64_le)(CPUARMState *env, uint64_t addr, #ifdef CONFIG_USER_ONLY /* ??? Enforce alignment. */ - uint64_t *haddr = g2h(addr); + uint64_t *haddr = g2h(env_cpu(env), addr); set_helper_retaddr(ra); o0 = ldq_le_p(haddr + 0); @@ -XXX,XX +XXX,XX @@ uint64_t HELPER(paired_cmpxchg64_be)(CPUARMState *env, uint64_t addr, #ifdef CONFIG_USER_ONLY /* ??? Enforce alignment. */ - uint64_t *haddr = g2h(addr); + uint64_t *haddr = g2h(env_cpu(env), addr); set_helper_retaddr(ra); o1 = ldq_be_p(haddr + 0); diff --git a/target/hppa/op_helper.c b/target/hppa/op_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/hppa/op_helper.c +++ b/target/hppa/op_helper.c @@ -XXX,XX +XXX,XX @@ static void atomic_store_3(CPUHPPAState *env, target_ulong addr, uint32_t val, #ifdef CONFIG_USER_ONLY uint32_t old, new, cmp; - uint32_t *haddr = g2h(addr - 1); + uint32_t *haddr = g2h(env_cpu(env), addr - 1); old = *haddr; while (1) { new = (old & ~mask) | (val & mask); diff --git a/target/i386/tcg/mem_helper.c b/target/i386/tcg/mem_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/i386/tcg/mem_helper.c +++ b/target/i386/tcg/mem_helper.c @@ -XXX,XX +XXX,XX @@ void helper_cmpxchg8b(CPUX86State *env, target_ulong a0) #ifdef CONFIG_USER_ONLY { - uint64_t *haddr = g2h(a0); + uint64_t *haddr = g2h(env_cpu(env), a0); cmpv = cpu_to_le64(cmpv); newv = cpu_to_le64(newv); oldv = qatomic_cmpxchg__nocheck(haddr, cmpv, newv); diff --git a/target/s390x/mem_helper.c b/target/s390x/mem_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/s390x/mem_helper.c +++ b/target/s390x/mem_helper.c @@ -XXX,XX +XXX,XX @@ static uint32_t do_csst(CPUS390XState *env, uint32_t r3, uint64_t a1, if (parallel) { #ifdef CONFIG_USER_ONLY - uint32_t *haddr = g2h(a1); + uint32_t *haddr = g2h(env_cpu(env), a1); ov = qatomic_cmpxchg__nocheck(haddr, cv, nv); #else TCGMemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mem_idx); @@ -XXX,XX +XXX,XX @@ static uint32_t do_csst(CPUS390XState *env, uint32_t r3, uint64_t a1, if (parallel) { #ifdef CONFIG_ATOMIC64 # ifdef CONFIG_USER_ONLY - uint64_t *haddr = g2h(a1); + uint64_t *haddr = g2h(env_cpu(env), a1); ov = qatomic_cmpxchg__nocheck(haddr, cv, nv); # else TCGMemOpIdx oi = make_memop_idx(MO_TEQ | MO_ALIGN, mem_idx); -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> We define target_mmap et al as untagged, so that they can be used from the binary loaders. Explicitly call cpu_untagged_addr for munmap, mprotect, mremap syscall entry points. Add a few comments for the syscalls that are exempted by the kernel's tagged-address-abi.rst. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-14-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/syscall.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -XXX,XX +XXX,XX @@ abi_long do_brk(abi_ulong new_brk) abi_long mapped_addr; abi_ulong new_alloc_size; + /* brk pointers are always untagged */ + DEBUGF_BRK("do_brk(" TARGET_ABI_FMT_lx ") -> ", new_brk); if (!new_brk) { @@ -XXX,XX +XXX,XX @@ static inline abi_ulong do_shmat(CPUArchState *cpu_env, int i,ret; abi_ulong shmlba; + /* shmat pointers are always untagged */ + /* find out the length of the shared memory segment */ ret = get_errno(shmctl(shmid, IPC_STAT, &shm_info)); if (is_error(ret)) { @@ -XXX,XX +XXX,XX @@ static inline abi_long do_shmdt(abi_ulong shmaddr) int i; abi_long rv; + /* shmdt pointers are always untagged */ + mmap_lock(); for (i = 0; i < N_SHM_REGIONS; ++i) { @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, v5, v6)); } #else + /* mmap pointers are always untagged */ ret = get_errno(target_mmap(arg1, arg2, arg3, target_to_host_bitmask(arg4, mmap_flags_tbl), arg5, @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, return get_errno(ret); #endif case TARGET_NR_munmap: + arg1 = cpu_untagged_addr(cpu, arg1); return get_errno(target_munmap(arg1, arg2)); case TARGET_NR_mprotect: + arg1 = cpu_untagged_addr(cpu, arg1); { TaskState *ts = cpu->opaque; /* Special hack to detect libc making the stack executable. */ @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, return get_errno(target_mprotect(arg1, arg2, arg3)); #ifdef TARGET_NR_mremap case TARGET_NR_mremap: + arg1 = cpu_untagged_addr(cpu, arg1); + /* mremap new_addr (arg5) is always untagged */ return get_errno(target_mremap(arg1, arg2, arg3, arg4, arg5)); #endif /* ??? msync/mlock/munlock are broken for softmmu. */ -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> We're currently open-coding the range check in access_ok; use guest_range_valid when size != 0. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-15-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/qemu.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ extern unsigned long guest_stack_size; static inline bool access_ok(int type, abi_ulong addr, abi_ulong size) { - if (!guest_addr_valid(addr)) { - return false; - } - if (size != 0 && - (addr + size - 1 < addr || - !guest_addr_valid(addr + size - 1))) { + if (size == 0 + ? !guest_addr_valid(addr) + : !guest_range_valid(addr, size)) { return false; } return page_check_range((target_ulong)addr, size, type) == 0; -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> The places that use these are better off using untagged addresses, so do not provide a tagged versions. Rename to make it clear about the address type. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-16-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu_ldst.h | 4 ++-- linux-user/qemu.h | 4 ++-- accel/tcg/user-exec.c | 3 ++- linux-user/mmap.c | 14 +++++++------- linux-user/syscall.c | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu_ldst.h +++ b/include/exec/cpu_ldst.h @@ -XXX,XX +XXX,XX @@ static inline void *g2h(CPUState *cs, abi_ptr x) return g2h_untagged(cpu_untagged_addr(cs, x)); } -static inline bool guest_addr_valid(abi_ulong x) +static inline bool guest_addr_valid_untagged(abi_ulong x) { return x <= GUEST_ADDR_MAX; } -static inline bool guest_range_valid(abi_ulong start, abi_ulong len) +static inline bool guest_range_valid_untagged(abi_ulong start, abi_ulong len) { return len - 1 <= GUEST_ADDR_MAX && start <= GUEST_ADDR_MAX - len + 1; } diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ extern unsigned long guest_stack_size; static inline bool access_ok(int type, abi_ulong addr, abi_ulong size) { if (size == 0 - ? !guest_addr_valid(addr) - : !guest_range_valid(addr, size)) { + ? !guest_addr_valid_untagged(addr) + : !guest_range_valid_untagged(addr, size)) { return false; } return page_check_range((target_ulong)addr, size, type) == 0; diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index XXXXXXX..XXXXXXX 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -XXX,XX +XXX,XX @@ static int probe_access_internal(CPUArchState *env, target_ulong addr, g_assert_not_reached(); } - if (!guest_addr_valid(addr) || page_check_range(addr, 1, flags) < 0) { + if (!guest_addr_valid_untagged(addr) || + page_check_range(addr, 1, flags) < 0) { if (nonfault) { return TLB_INVALID_MASK; } else { diff --git a/linux-user/mmap.c b/linux-user/mmap.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -XXX,XX +XXX,XX @@ int target_mprotect(abi_ulong start, abi_ulong len, int target_prot) } len = TARGET_PAGE_ALIGN(len); end = start + len; - if (!guest_range_valid(start, len)) { + if (!guest_range_valid_untagged(start, len)) { return -TARGET_ENOMEM; } if (len == 0) { @@ -XXX,XX +XXX,XX @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, * It can fail only on 64-bit host with 32-bit target. * On any other target/host host mmap() handles this error correctly. */ - if (end < start || !guest_range_valid(start, len)) { + if (end < start || !guest_range_valid_untagged(start, len)) { errno = ENOMEM; goto fail; } @@ -XXX,XX +XXX,XX @@ int target_munmap(abi_ulong start, abi_ulong len) if (start & ~TARGET_PAGE_MASK) return -TARGET_EINVAL; len = TARGET_PAGE_ALIGN(len); - if (len == 0 || !guest_range_valid(start, len)) { + if (len == 0 || !guest_range_valid_untagged(start, len)) { return -TARGET_EINVAL; } @@ -XXX,XX +XXX,XX @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, int prot; void *host_addr; - if (!guest_range_valid(old_addr, old_size) || + if (!guest_range_valid_untagged(old_addr, old_size) || ((flags & MREMAP_FIXED) && - !guest_range_valid(new_addr, new_size)) || + !guest_range_valid_untagged(new_addr, new_size)) || ((flags & MREMAP_MAYMOVE) == 0 && - !guest_range_valid(old_addr, new_size))) { + !guest_range_valid_untagged(old_addr, new_size))) { errno = ENOMEM; return -1; } @@ -XXX,XX +XXX,XX @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, if (host_addr != MAP_FAILED) { /* Check if address fits target address space */ - if (!guest_range_valid(h2g(host_addr), new_size)) { + if (!guest_range_valid_untagged(h2g(host_addr), new_size)) { /* Revert mremap() changes */ host_addr = mremap(g2h_untagged(old_addr), new_size, old_size, flags); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -XXX,XX +XXX,XX @@ static inline abi_ulong do_shmat(CPUArchState *cpu_env, return -TARGET_EINVAL; } } - if (!guest_range_valid(shmaddr, shm_info.shm_segsz)) { + if (!guest_range_valid_untagged(shmaddr, shm_info.shm_segsz)) { return -TARGET_EINVAL; } -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Provide both tagged and untagged versions of access_ok. In a few places use thread_cpu, as the user is several callees removed from do_syscall1. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-17-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/qemu.h | 11 +++++++++-- linux-user/elfload.c | 2 +- linux-user/hppa/cpu_loop.c | 8 ++++---- linux-user/i386/cpu_loop.c | 2 +- linux-user/i386/signal.c | 5 +++-- linux-user/syscall.c | 9 ++++++--- 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ extern unsigned long guest_stack_size; #define VERIFY_READ PAGE_READ #define VERIFY_WRITE (PAGE_READ | PAGE_WRITE) -static inline bool access_ok(int type, abi_ulong addr, abi_ulong size) +static inline bool access_ok_untagged(int type, abi_ulong addr, abi_ulong size) { if (size == 0 ? !guest_addr_valid_untagged(addr) @@ -XXX,XX +XXX,XX @@ static inline bool access_ok(int type, abi_ulong addr, abi_ulong size) return page_check_range((target_ulong)addr, size, type) == 0; } +static inline bool access_ok(CPUState *cpu, int type, + abi_ulong addr, abi_ulong size) +{ + return access_ok_untagged(type, cpu_untagged_addr(cpu, addr), size); +} + /* NOTE __get_user and __put_user use host pointers and don't check access. These are usually used to access struct data members once the struct has been locked - usually with lock_user_struct. */ @@ -XXX,XX +XXX,XX @@ abi_long copy_to_user(abi_ulong gaddr, void *hptr, size_t len); host area will have the same contents as the guest. */ static inline void *lock_user(int type, abi_ulong guest_addr, long len, int copy) { - if (!access_ok(type, guest_addr, len)) + if (!access_ok_untagged(type, guest_addr, len)) { return NULL; + } #ifdef DEBUG_REMAP { void *addr; diff --git a/linux-user/elfload.c b/linux-user/elfload.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -XXX,XX +XXX,XX @@ static int vma_get_mapping_count(const struct mm_struct *mm) static abi_ulong vma_dump_size(const struct vm_area_struct *vma) { /* if we cannot even read the first page, skip it */ - if (!access_ok(VERIFY_READ, vma->vma_start, TARGET_PAGE_SIZE)) + if (!access_ok_untagged(VERIFY_READ, vma->vma_start, TARGET_PAGE_SIZE)) return (0); /* diff --git a/linux-user/hppa/cpu_loop.c b/linux-user/hppa/cpu_loop.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/hppa/cpu_loop.c +++ b/linux-user/hppa/cpu_loop.c @@ -XXX,XX +XXX,XX @@ static abi_ulong hppa_lws(CPUHPPAState *env) return -TARGET_ENOSYS; case 0: /* elf32 atomic 32bit cmpxchg */ - if ((addr & 3) || !access_ok(VERIFY_WRITE, addr, 4)) { + if ((addr & 3) || !access_ok(cs, VERIFY_WRITE, addr, 4)) { return -TARGET_EFAULT; } old = tswap32(old); @@ -XXX,XX +XXX,XX @@ static abi_ulong hppa_lws(CPUHPPAState *env) return -TARGET_ENOSYS; } if (((addr | old | new) & ((1 << size) - 1)) - || !access_ok(VERIFY_WRITE, addr, 1 << size) - || !access_ok(VERIFY_READ, old, 1 << size) - || !access_ok(VERIFY_READ, new, 1 << size)) { + || !access_ok(cs, VERIFY_WRITE, addr, 1 << size) + || !access_ok(cs, VERIFY_READ, old, 1 << size) + || !access_ok(cs, VERIFY_READ, new, 1 << size)) { return -TARGET_EFAULT; } /* Note that below we use host-endian loads so that the cmpxchg diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/i386/cpu_loop.c +++ b/linux-user/i386/cpu_loop.c @@ -XXX,XX +XXX,XX @@ static bool write_ok_or_segv(CPUX86State *env, abi_ptr addr, size_t len) * For all the vsyscalls, NULL means "don't write anything" not * "write it at address 0". */ - if (addr == 0 || access_ok(VERIFY_WRITE, addr, len)) { + if (addr == 0 || access_ok(env_cpu(env), VERIFY_WRITE, addr, len)) { return true; } diff --git a/linux-user/i386/signal.c b/linux-user/i386/signal.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/i386/signal.c +++ b/linux-user/i386/signal.c @@ -XXX,XX +XXX,XX @@ restore_sigcontext(CPUX86State *env, struct target_sigcontext *sc) fpstate_addr = tswapl(sc->fpstate); if (fpstate_addr != 0) { - if (!access_ok(VERIFY_READ, fpstate_addr, - sizeof(struct target_fpstate))) + if (!access_ok(env_cpu(env), VERIFY_READ, fpstate_addr, + sizeof(struct target_fpstate))) { goto badframe; + } #ifndef TARGET_X86_64 cpu_x86_frstor(env, fpstate_addr, 1); #else diff --git a/linux-user/syscall.c b/linux-user/syscall.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -XXX,XX +XXX,XX @@ static abi_long do_accept4(int fd, abi_ulong target_addr, return -TARGET_EINVAL; } - if (!access_ok(VERIFY_WRITE, target_addr, addrlen)) + if (!access_ok(thread_cpu, VERIFY_WRITE, target_addr, addrlen)) { return -TARGET_EFAULT; + } addr = alloca(addrlen); @@ -XXX,XX +XXX,XX @@ static abi_long do_getpeername(int fd, abi_ulong target_addr, return -TARGET_EINVAL; } - if (!access_ok(VERIFY_WRITE, target_addr, addrlen)) + if (!access_ok(thread_cpu, VERIFY_WRITE, target_addr, addrlen)) { return -TARGET_EFAULT; + } addr = alloca(addrlen); @@ -XXX,XX +XXX,XX @@ static abi_long do_getsockname(int fd, abi_ulong target_addr, return -TARGET_EINVAL; } - if (!access_ok(VERIFY_WRITE, target_addr, addrlen)) + if (!access_ok(thread_cpu, VERIFY_WRITE, target_addr, addrlen)) { return -TARGET_EFAULT; + } addr = alloca(addrlen); -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> These functions are not small, except for unlock_user without debugging enabled. Move them out of line, and add missing braces on the way. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-id: 20210212184902.1251044-18-richard.henderson@linaro.org [PMM: fixed the sense of an ifdef test in qemu.h] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/qemu.h | 47 +++++++------------------------------------- linux-user/uaccess.c | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ abi_long copy_to_user(abi_ulong gaddr, void *hptr, size_t len); /* Lock an area of guest memory into the host. If copy is true then the host area will have the same contents as the guest. */ -static inline void *lock_user(int type, abi_ulong guest_addr, long len, int copy) -{ - if (!access_ok_untagged(type, guest_addr, len)) { - return NULL; - } -#ifdef DEBUG_REMAP - { - void *addr; - addr = g_malloc(len); - if (copy) - memcpy(addr, g2h(guest_addr), len); - else - memset(addr, 0, len); - return addr; - } -#else - return g2h_untagged(guest_addr); -#endif -} +void *lock_user(int type, abi_ulong guest_addr, long len, int copy); /* Unlock an area of guest memory. The first LEN bytes must be flushed back to guest memory. host_ptr = NULL is explicitly allowed and does nothing. */ -static inline void unlock_user(void *host_ptr, abi_ulong guest_addr, - long len) -{ - -#ifdef DEBUG_REMAP - if (!host_ptr) - return; - if (host_ptr == g2h_untagged(guest_addr)) - return; - if (len > 0) - memcpy(g2h_untagged(guest_addr), host_ptr, len); - g_free(host_ptr); +#ifndef DEBUG_REMAP +static inline void unlock_user(void *host_ptr, abi_ulong guest_addr, long len) +{ } +#else +void unlock_user(void *host_ptr, abi_ulong guest_addr, long len); #endif -} /* Return the length of a string in target memory or -TARGET_EFAULT if access error. */ abi_long target_strlen(abi_ulong gaddr); /* Like lock_user but for null terminated strings. */ -static inline void *lock_user_string(abi_ulong guest_addr) -{ - abi_long len; - len = target_strlen(guest_addr); - if (len < 0) - return NULL; - return lock_user(VERIFY_READ, guest_addr, (long)(len + 1), 1); -} +void *lock_user_string(abi_ulong guest_addr); /* Helper macros for locking/unlocking a target struct. */ #define lock_user_struct(type, host_ptr, guest_addr, copy) \ diff --git a/linux-user/uaccess.c b/linux-user/uaccess.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/uaccess.c +++ b/linux-user/uaccess.c @@ -XXX,XX +XXX,XX @@ #include "qemu.h" +void *lock_user(int type, abi_ulong guest_addr, long len, int copy) +{ + if (!access_ok_untagged(type, guest_addr, len)) { + return NULL; + } +#ifdef DEBUG_REMAP + { + void *addr; + addr = g_malloc(len); + if (copy) { + memcpy(addr, g2h(guest_addr), len); + } else { + memset(addr, 0, len); + } + return addr; + } +#else + return g2h_untagged(guest_addr); +#endif +} + +#ifdef DEBUG_REMAP +void unlock_user(void *host_ptr, abi_ulong guest_addr, long len); +{ + if (!host_ptr) { + return; + } + if (host_ptr == g2h_untagged(guest_addr)) { + return; + } + if (len > 0) { + memcpy(g2h_untagged(guest_addr), host_ptr, len); + } + g_free(host_ptr); +} +#endif + +void *lock_user_string(abi_ulong guest_addr) +{ + abi_long len = target_strlen(guest_addr); + if (len < 0) { + return NULL; + } + return lock_user(VERIFY_READ, guest_addr, (long)(len + 1), 1); +} + /* copy_from_user() and copy_to_user() are usually used to copy data * buffers between the target and host. These internally perform * locking/unlocking of the memory. -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> For copy_*_user, only 0 and -TARGET_EFAULT are returned; no need to involve abi_long. Use size_t for lengths. Use bool for the lock_user copy argument. Use ssize_t for target_strlen, because we can't overflow the host memory space. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-id: 20210212184902.1251044-19-richard.henderson@linaro.org [PMM: moved fix for ifdef error to previous commit] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/qemu.h | 12 +++++------- linux-user/uaccess.c | 45 ++++++++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -XXX,XX +XXX,XX @@ #include "exec/cpu_ldst.h" #undef DEBUG_REMAP -#ifdef DEBUG_REMAP -#endif /* DEBUG_REMAP */ #include "exec/user/abitypes.h" @@ -XXX,XX +XXX,XX @@ static inline bool access_ok(CPUState *cpu, int type, * buffers between the target and host. These internally perform * locking/unlocking of the memory. */ -abi_long copy_from_user(void *hptr, abi_ulong gaddr, size_t len); -abi_long copy_to_user(abi_ulong gaddr, void *hptr, size_t len); +int copy_from_user(void *hptr, abi_ulong gaddr, size_t len); +int copy_to_user(abi_ulong gaddr, void *hptr, size_t len); /* Functions for accessing guest memory. The tget and tput functions read/write single values, byteswapping as necessary. The lock_user function @@ -XXX,XX +XXX,XX @@ abi_long copy_to_user(abi_ulong gaddr, void *hptr, size_t len); /* Lock an area of guest memory into the host. If copy is true then the host area will have the same contents as the guest. */ -void *lock_user(int type, abi_ulong guest_addr, long len, int copy); +void *lock_user(int type, abi_ulong guest_addr, size_t len, bool copy); /* Unlock an area of guest memory. The first LEN bytes must be flushed back to guest memory. host_ptr = NULL is explicitly allowed and does nothing. */ #ifndef DEBUG_REMAP -static inline void unlock_user(void *host_ptr, abi_ulong guest_addr, long len) +static inline void unlock_user(void *host_ptr, abi_ulong guest_addr, size_t len) { } #else void unlock_user(void *host_ptr, abi_ulong guest_addr, long len); @@ -XXX,XX +XXX,XX @@ void unlock_user(void *host_ptr, abi_ulong guest_addr, long len); /* Return the length of a string in target memory or -TARGET_EFAULT if access error. */ -abi_long target_strlen(abi_ulong gaddr); +ssize_t target_strlen(abi_ulong gaddr); /* Like lock_user but for null terminated strings. */ void *lock_user_string(abi_ulong guest_addr); diff --git a/linux-user/uaccess.c b/linux-user/uaccess.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/uaccess.c +++ b/linux-user/uaccess.c @@ -XXX,XX +XXX,XX @@ #include "qemu.h" -void *lock_user(int type, abi_ulong guest_addr, long len, int copy) +void *lock_user(int type, abi_ulong guest_addr, size_t len, bool copy) { if (!access_ok_untagged(type, guest_addr, len)) { return NULL; @@ -XXX,XX +XXX,XX @@ void *lock_user(int type, abi_ulong guest_addr, long len, int copy) } #ifdef DEBUG_REMAP -void unlock_user(void *host_ptr, abi_ulong guest_addr, long len); +void unlock_user(void *host_ptr, abi_ulong guest_addr, size_t len); { if (!host_ptr) { return; @@ -XXX,XX +XXX,XX @@ void unlock_user(void *host_ptr, abi_ulong guest_addr, long len); if (host_ptr == g2h_untagged(guest_addr)) { return; } - if (len > 0) { + if (len != 0) { memcpy(g2h_untagged(guest_addr), host_ptr, len); } g_free(host_ptr); @@ -XXX,XX +XXX,XX @@ void unlock_user(void *host_ptr, abi_ulong guest_addr, long len); void *lock_user_string(abi_ulong guest_addr) { - abi_long len = target_strlen(guest_addr); + ssize_t len = target_strlen(guest_addr); if (len < 0) { return NULL; } - return lock_user(VERIFY_READ, guest_addr, (long)(len + 1), 1); + return lock_user(VERIFY_READ, guest_addr, (size_t)len + 1, 1); } /* copy_from_user() and copy_to_user() are usually used to copy data * buffers between the target and host. These internally perform * locking/unlocking of the memory. */ -abi_long copy_from_user(void *hptr, abi_ulong gaddr, size_t len) +int copy_from_user(void *hptr, abi_ulong gaddr, size_t len) { - abi_long ret = 0; - void *ghptr; + int ret = 0; + void *ghptr = lock_user(VERIFY_READ, gaddr, len, 1); - if ((ghptr = lock_user(VERIFY_READ, gaddr, len, 1))) { + if (ghptr) { memcpy(hptr, ghptr, len); unlock_user(ghptr, gaddr, 0); - } else + } else { ret = -TARGET_EFAULT; - + } return ret; } - -abi_long copy_to_user(abi_ulong gaddr, void *hptr, size_t len) +int copy_to_user(abi_ulong gaddr, void *hptr, size_t len) { - abi_long ret = 0; - void *ghptr; + int ret = 0; + void *ghptr = lock_user(VERIFY_WRITE, gaddr, len, 0); - if ((ghptr = lock_user(VERIFY_WRITE, gaddr, len, 0))) { + if (ghptr) { memcpy(ghptr, hptr, len); unlock_user(ghptr, gaddr, len); - } else + } else { ret = -TARGET_EFAULT; + } return ret; } /* Return the length of a string in target memory or -TARGET_EFAULT if access error */ -abi_long target_strlen(abi_ulong guest_addr1) +ssize_t target_strlen(abi_ulong guest_addr1) { uint8_t *ptr; abi_ulong guest_addr; - int max_len, len; + size_t max_len, len; guest_addr = guest_addr1; for(;;) { @@ -XXX,XX +XXX,XX @@ abi_long target_strlen(abi_ulong guest_addr1) unlock_user(ptr, guest_addr, 0); guest_addr += len; /* we don't allow wrapping or integer overflow */ - if (guest_addr == 0 || - (guest_addr - guest_addr1) > 0x7fffffff) + if (guest_addr == 0 || (guest_addr - guest_addr1) > 0x7fffffff) { return -TARGET_EFAULT; - if (len != max_len) + } + if (len != max_len) { break; + } } return guest_addr - guest_addr1; } -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Resolve the untagged address once, using thread_cpu. Tidy the DEBUG_REMAP code using glib routines. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-20-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/uaccess.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/linux-user/uaccess.c b/linux-user/uaccess.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/uaccess.c +++ b/linux-user/uaccess.c @@ -XXX,XX +XXX,XX @@ void *lock_user(int type, abi_ulong guest_addr, size_t len, bool copy) { + void *host_addr; + + guest_addr = cpu_untagged_addr(thread_cpu, guest_addr); if (!access_ok_untagged(type, guest_addr, len)) { return NULL; } + host_addr = g2h_untagged(guest_addr); #ifdef DEBUG_REMAP - { - void *addr; - addr = g_malloc(len); - if (copy) { - memcpy(addr, g2h(guest_addr), len); - } else { - memset(addr, 0, len); - } - return addr; + if (copy) { + host_addr = g_memdup(host_addr, len); + } else { + host_addr = g_malloc0(len); } -#else - return g2h_untagged(guest_addr); #endif + return host_addr; } #ifdef DEBUG_REMAP void unlock_user(void *host_ptr, abi_ulong guest_addr, size_t len); { + void *host_ptr_conv; + if (!host_ptr) { return; } - if (host_ptr == g2h_untagged(guest_addr)) { + host_ptr_conv = g2h(thread_cpu, guest_addr); + if (host_ptr == host_ptr_conv) { return; } if (len != 0) { - memcpy(g2h_untagged(guest_addr), host_ptr, len); + memcpy(host_ptr_conv, host_ptr, len); } g_free(host_ptr); } -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> This is the prctl bit that controls whether syscalls accept tagged addresses. See Documentation/arm64/tagged-address-abi.rst in the linux kernel. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-21-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/aarch64/target_syscall.h | 4 ++++ target/arm/cpu-param.h | 3 +++ target/arm/cpu.h | 31 +++++++++++++++++++++++++++++ linux-user/syscall.c | 24 ++++++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/linux-user/aarch64/target_syscall.h b/linux-user/aarch64/target_syscall.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/target_syscall.h +++ b/linux-user/aarch64/target_syscall.h @@ -XXX,XX +XXX,XX @@ struct target_pt_regs { # define TARGET_PR_PAC_APDBKEY (1 << 3) # define TARGET_PR_PAC_APGAKEY (1 << 4) +#define TARGET_PR_SET_TAGGED_ADDR_CTRL 55 +#define TARGET_PR_GET_TAGGED_ADDR_CTRL 56 +# define TARGET_PR_TAGGED_ADDR_ENABLE (1UL << 0) + #endif /* AARCH64_TARGET_SYSCALL_H */ diff --git a/target/arm/cpu-param.h b/target/arm/cpu-param.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu-param.h +++ b/target/arm/cpu-param.h @@ -XXX,XX +XXX,XX @@ #ifdef CONFIG_USER_ONLY #define TARGET_PAGE_BITS 12 +# ifdef TARGET_AARCH64 +# define TARGET_TAGGED_ADDRESSES +# endif #else /* * ARMv7 and later CPUs have 4K pages minimum, but ARMv5 and v6 diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUARMState { const struct arm_boot_info *boot_info; /* Store GICv3CPUState to access from this struct */ void *gicv3state; + +#ifdef TARGET_TAGGED_ADDRESSES + /* Linux syscall tagged address support */ + bool tagged_addr_enable; +#endif } CPUARMState; static inline void set_feature(CPUARMState *env, int feature) @@ -XXX,XX +XXX,XX @@ static inline MemTxAttrs *typecheck_memtxattrs(MemTxAttrs *x) */ #define PAGE_BTI PAGE_TARGET_1 +#ifdef TARGET_TAGGED_ADDRESSES +/** + * cpu_untagged_addr: + * @cs: CPU context + * @x: tagged address + * + * Remove any address tag from @x. This is explicitly related to the + * linux syscall TIF_TAGGED_ADDR setting, not TBI in general. + * + * There should be a better place to put this, but we need this in + * include/exec/cpu_ldst.h, and not some place linux-user specific. + */ +static inline target_ulong cpu_untagged_addr(CPUState *cs, target_ulong x) +{ + ARMCPU *cpu = ARM_CPU(cs); + if (cpu->env.tagged_addr_enable) { + /* + * TBI is enabled for userspace but not kernelspace addresses. + * Only clear the tag if bit 55 is clear. + */ + x &= sextract64(x, 0, 56); + } + return x; +} +#endif + /* * Naming convention for isar_feature functions: * Functions which test 32-bit ID registers should have _aa32_ in diff --git a/linux-user/syscall.c b/linux-user/syscall.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, } } return -TARGET_EINVAL; + case TARGET_PR_SET_TAGGED_ADDR_CTRL: + { + abi_ulong valid_mask = TARGET_PR_TAGGED_ADDR_ENABLE; + CPUARMState *env = cpu_env; + + if ((arg2 & ~valid_mask) || arg3 || arg4 || arg5) { + return -TARGET_EINVAL; + } + env->tagged_addr_enable = arg2 & TARGET_PR_TAGGED_ADDR_ENABLE; + return 0; + } + case TARGET_PR_GET_TAGGED_ADDR_CTRL: + { + abi_long ret = 0; + CPUARMState *env = cpu_env; + + if (arg2 || arg3 || arg4 || arg5) { + return -TARGET_EINVAL; + } + if (env->tagged_addr_enable) { + ret |= TARGET_PR_TAGGED_ADDR_ENABLE; + } + return ret; + } #endif /* AARCH64 */ case PR_GET_SECCOMP: case PR_SET_SECCOMP: -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Use simple arithmetic instead of a conditional move when tbi0 != tbi1. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-22-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/translate-a64.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/translate-a64.c +++ b/target/arm/translate-a64.c @@ -XXX,XX +XXX,XX @@ static void gen_top_byte_ignore(DisasContext *s, TCGv_i64 dst, /* Sign-extend from bit 55. */ tcg_gen_sextract_i64(dst, src, 0, 56); - if (tbi != 3) { - TCGv_i64 tcg_zero = tcg_const_i64(0); - - /* - * The two TBI bits differ. - * If tbi0, then !tbi1: only use the extension if positive. - * if !tbi0, then tbi1: only use the extension if negative. - */ - tcg_gen_movcond_i64(tbi == 1 ? TCG_COND_GE : TCG_COND_LT, - dst, dst, tcg_zero, dst, src); - tcg_temp_free_i64(tcg_zero); + switch (tbi) { + case 1: + /* tbi0 but !tbi1: only use the extension if positive */ + tcg_gen_and_i64(dst, dst, src); + break; + case 2: + /* !tbi0 but tbi1: only use the extension if negative */ + tcg_gen_or_i64(dst, dst, src); + break; + case 3: + /* tbi0 and tbi1: always use the extension */ + break; + default: + g_assert_not_reached(); } } } -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> We were fudging TBI1 enabled to speed up the generated code. Now that we've improved the code generation, remove this. Also, tidy the comment to reflect the current code. The pauth test was testing a kernel address (-1) and making incorrect assumptions about TBI1; stick to userland addresses. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-23-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/internals.h | 4 ++-- target/arm/cpu.c | 10 +++------- tests/tcg/aarch64/pauth-2.c | 1 - 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/target/arm/internals.h b/target/arm/internals.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -XXX,XX +XXX,XX @@ static inline bool tcma_check(uint32_t desc, int bit55, int ptr_tag) */ static inline uint64_t useronly_clean_ptr(uint64_t ptr) { - /* TBI is known to be enabled. */ #ifdef CONFIG_USER_ONLY - ptr = sextract64(ptr, 0, 56); + /* TBI0 is known to be enabled, while TBI1 is disabled. */ + ptr &= sextract64(ptr, 0, 56); #endif return ptr; } diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ static void arm_cpu_reset(DeviceState *dev) env->vfp.zcr_el[1] = MIN(cpu->sve_max_vq - 1, 3); } /* - * Enable TBI0 and TBI1. While the real kernel only enables TBI0, - * turning on both here will produce smaller code and otherwise - * make no difference to the user-level emulation. - * - * In sve_probe_page, we assume that this is set. - * Do not modify this without other changes. + * Enable TBI0 but not TBI1. + * Note that this must match useronly_clean_ptr. */ - env->cp15.tcr_el[1].raw_tcr = (3ULL << 37); + env->cp15.tcr_el[1].raw_tcr = (1ULL << 37); #else /* Reset into the highest available EL */ if (arm_feature(env, ARM_FEATURE_EL3)) { diff --git a/tests/tcg/aarch64/pauth-2.c b/tests/tcg/aarch64/pauth-2.c index XXXXXXX..XXXXXXX 100644 --- a/tests/tcg/aarch64/pauth-2.c +++ b/tests/tcg/aarch64/pauth-2.c @@ -XXX,XX +XXX,XX @@ void do_test(uint64_t value) int main() { do_test(0); - do_test(-1); do_test(0xda004acedeadbeefull); return 0; } -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> These prctl fields are required for the function of MTE. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-24-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/aarch64/target_syscall.h | 9 ++++++ linux-user/syscall.c | 43 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/linux-user/aarch64/target_syscall.h b/linux-user/aarch64/target_syscall.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/target_syscall.h +++ b/linux-user/aarch64/target_syscall.h @@ -XXX,XX +XXX,XX @@ struct target_pt_regs { #define TARGET_PR_SET_TAGGED_ADDR_CTRL 55 #define TARGET_PR_GET_TAGGED_ADDR_CTRL 56 # define TARGET_PR_TAGGED_ADDR_ENABLE (1UL << 0) +/* MTE tag check fault modes */ +# define TARGET_PR_MTE_TCF_SHIFT 1 +# define TARGET_PR_MTE_TCF_NONE (0UL << TARGET_PR_MTE_TCF_SHIFT) +# define TARGET_PR_MTE_TCF_SYNC (1UL << TARGET_PR_MTE_TCF_SHIFT) +# define TARGET_PR_MTE_TCF_ASYNC (2UL << TARGET_PR_MTE_TCF_SHIFT) +# define TARGET_PR_MTE_TCF_MASK (3UL << TARGET_PR_MTE_TCF_SHIFT) +/* MTE tag inclusion mask */ +# define TARGET_PR_MTE_TAG_SHIFT 3 +# define TARGET_PR_MTE_TAG_MASK (0xffffUL << TARGET_PR_MTE_TAG_SHIFT) #endif /* AARCH64_TARGET_SYSCALL_H */ diff --git a/linux-user/syscall.c b/linux-user/syscall.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, { abi_ulong valid_mask = TARGET_PR_TAGGED_ADDR_ENABLE; CPUARMState *env = cpu_env; + ARMCPU *cpu = env_archcpu(env); + + if (cpu_isar_feature(aa64_mte, cpu)) { + valid_mask |= TARGET_PR_MTE_TCF_MASK; + valid_mask |= TARGET_PR_MTE_TAG_MASK; + } if ((arg2 & ~valid_mask) || arg3 || arg4 || arg5) { return -TARGET_EINVAL; } env->tagged_addr_enable = arg2 & TARGET_PR_TAGGED_ADDR_ENABLE; + + if (cpu_isar_feature(aa64_mte, cpu)) { + switch (arg2 & TARGET_PR_MTE_TCF_MASK) { + case TARGET_PR_MTE_TCF_NONE: + case TARGET_PR_MTE_TCF_SYNC: + case TARGET_PR_MTE_TCF_ASYNC: + break; + default: + return -EINVAL; + } + + /* + * Write PR_MTE_TCF to SCTLR_EL1[TCF0]. + * Note that the syscall values are consistent with hw. + */ + env->cp15.sctlr_el[1] = + deposit64(env->cp15.sctlr_el[1], 38, 2, + arg2 >> TARGET_PR_MTE_TCF_SHIFT); + + /* + * Write PR_MTE_TAG to GCR_EL1[Exclude]. + * Note that the syscall uses an include mask, + * and hardware uses an exclude mask -- invert. + */ + env->cp15.gcr_el1 = + deposit64(env->cp15.gcr_el1, 0, 16, + ~arg2 >> TARGET_PR_MTE_TAG_SHIFT); + arm_rebuild_hflags(env); + } return 0; } case TARGET_PR_GET_TAGGED_ADDR_CTRL: { abi_long ret = 0; CPUARMState *env = cpu_env; + ARMCPU *cpu = env_archcpu(env); if (arg2 || arg3 || arg4 || arg5) { return -TARGET_EINVAL; @@ -XXX,XX +XXX,XX @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, if (env->tagged_addr_enable) { ret |= TARGET_PR_TAGGED_ADDR_ENABLE; } + if (cpu_isar_feature(aa64_mte, cpu)) { + /* See above. */ + ret |= (extract64(env->cp15.sctlr_el[1], 38, 2) + << TARGET_PR_MTE_TCF_SHIFT); + ret = deposit64(ret, TARGET_PR_MTE_TAG_SHIFT, 16, + ~env->cp15.gcr_el1); + } return ret; } #endif /* AARCH64 */ -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Remember the PROT_MTE bit as PAGE_MTE/PAGE_TARGET_2. Otherwise this does not yet have effect. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-25-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/exec/cpu-all.h | 1 + linux-user/syscall_defs.h | 1 + target/arm/cpu.h | 1 + linux-user/mmap.c | 22 ++++++++++++++-------- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h index XXXXXXX..XXXXXXX 100644 --- a/include/exec/cpu-all.h +++ b/include/exec/cpu-all.h @@ -XXX,XX +XXX,XX @@ extern intptr_t qemu_host_page_mask; #endif /* Target-specific bits that will be used via page_get_flags(). */ #define PAGE_TARGET_1 0x0080 +#define PAGE_TARGET_2 0x0200 #if defined(CONFIG_USER_ONLY) void page_dump(FILE *f); diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -XXX,XX +XXX,XX @@ struct target_winsize { #ifdef TARGET_AARCH64 #define TARGET_PROT_BTI 0x10 +#define TARGET_PROT_MTE 0x20 #endif /* Common */ diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ static inline MemTxAttrs *typecheck_memtxattrs(MemTxAttrs *x) * AArch64 usage of the PAGE_TARGET_* bits for linux-user. */ #define PAGE_BTI PAGE_TARGET_1 +#define PAGE_MTE PAGE_TARGET_2 #ifdef TARGET_TAGGED_ADDRESSES /** diff --git a/linux-user/mmap.c b/linux-user/mmap.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -XXX,XX +XXX,XX @@ static int validate_prot_to_pageflags(int *host_prot, int prot) | (prot & PROT_EXEC ? PROT_READ : 0); #ifdef TARGET_AARCH64 - /* - * The PROT_BTI bit is only accepted if the cpu supports the feature. - * Since this is the unusual case, don't bother checking unless - * the bit has been requested. If set and valid, record the bit - * within QEMU's page_flags. - */ - if (prot & TARGET_PROT_BTI) { + { ARMCPU *cpu = ARM_CPU(thread_cpu); - if (cpu_isar_feature(aa64_bti, cpu)) { + + /* + * The PROT_BTI bit is only accepted if the cpu supports the feature. + * Since this is the unusual case, don't bother checking unless + * the bit has been requested. If set and valid, record the bit + * within QEMU's page_flags. + */ + if ((prot & TARGET_PROT_BTI) && cpu_isar_feature(aa64_bti, cpu)) { valid |= TARGET_PROT_BTI; page_flags |= PAGE_BTI; } + /* Similarly for the PROT_MTE bit. */ + if ((prot & TARGET_PROT_MTE) && cpu_isar_feature(aa64_mte, cpu)) { + valid |= TARGET_PROT_MTE; + page_flags |= PAGE_MTE; + } } #endif -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Move everything related to syndromes to a new file, which can be shared with linux-user. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-id: 20210212184902.1251044-26-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/internals.h | 245 +----------------------------------- target/arm/syndrome.h | 273 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 244 deletions(-) create mode 100644 target/arm/syndrome.h diff --git a/target/arm/internals.h b/target/arm/internals.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -XXX,XX +XXX,XX @@ #define TARGET_ARM_INTERNALS_H #include "hw/registerfields.h" +#include "syndrome.h" /* register banks for CPU modes */ #define BANK_USRSYS 0 @@ -XXX,XX +XXX,XX @@ static inline bool extended_addresses_enabled(CPUARMState *env) (arm_feature(env, ARM_FEATURE_LPAE) && (tcr->raw_tcr & TTBCR_EAE)); } -/* Valid Syndrome Register EC field values */ -enum arm_exception_class { - EC_UNCATEGORIZED = 0x00, - EC_WFX_TRAP = 0x01, - EC_CP15RTTRAP = 0x03, - EC_CP15RRTTRAP = 0x04, - EC_CP14RTTRAP = 0x05, - EC_CP14DTTRAP = 0x06, - EC_ADVSIMDFPACCESSTRAP = 0x07, - EC_FPIDTRAP = 0x08, - EC_PACTRAP = 0x09, - EC_CP14RRTTRAP = 0x0c, - EC_BTITRAP = 0x0d, - EC_ILLEGALSTATE = 0x0e, - EC_AA32_SVC = 0x11, - EC_AA32_HVC = 0x12, - EC_AA32_SMC = 0x13, - EC_AA64_SVC = 0x15, - EC_AA64_HVC = 0x16, - EC_AA64_SMC = 0x17, - EC_SYSTEMREGISTERTRAP = 0x18, - EC_SVEACCESSTRAP = 0x19, - EC_INSNABORT = 0x20, - EC_INSNABORT_SAME_EL = 0x21, - EC_PCALIGNMENT = 0x22, - EC_DATAABORT = 0x24, - EC_DATAABORT_SAME_EL = 0x25, - EC_SPALIGNMENT = 0x26, - EC_AA32_FPTRAP = 0x28, - EC_AA64_FPTRAP = 0x2c, - EC_SERROR = 0x2f, - EC_BREAKPOINT = 0x30, - EC_BREAKPOINT_SAME_EL = 0x31, - EC_SOFTWARESTEP = 0x32, - EC_SOFTWARESTEP_SAME_EL = 0x33, - EC_WATCHPOINT = 0x34, - EC_WATCHPOINT_SAME_EL = 0x35, - EC_AA32_BKPT = 0x38, - EC_VECTORCATCH = 0x3a, - EC_AA64_BKPT = 0x3c, -}; - -#define ARM_EL_EC_SHIFT 26 -#define ARM_EL_IL_SHIFT 25 -#define ARM_EL_ISV_SHIFT 24 -#define ARM_EL_IL (1 << ARM_EL_IL_SHIFT) -#define ARM_EL_ISV (1 << ARM_EL_ISV_SHIFT) - -static inline uint32_t syn_get_ec(uint32_t syn) -{ - return syn >> ARM_EL_EC_SHIFT; -} - -/* Utility functions for constructing various kinds of syndrome value. - * Note that in general we follow the AArch64 syndrome values; in a - * few cases the value in HSR for exceptions taken to AArch32 Hyp - * mode differs slightly, and we fix this up when populating HSR in - * arm_cpu_do_interrupt_aarch32_hyp(). - * The exception is FP/SIMD access traps -- these report extra information - * when taking an exception to AArch32. For those we include the extra coproc - * and TA fields, and mask them out when taking the exception to AArch64. - */ -static inline uint32_t syn_uncategorized(void) -{ - return (EC_UNCATEGORIZED << ARM_EL_EC_SHIFT) | ARM_EL_IL; -} - -static inline uint32_t syn_aa64_svc(uint32_t imm16) -{ - return (EC_AA64_SVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); -} - -static inline uint32_t syn_aa64_hvc(uint32_t imm16) -{ - return (EC_AA64_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); -} - -static inline uint32_t syn_aa64_smc(uint32_t imm16) -{ - return (EC_AA64_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); -} - -static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_16bit) -{ - return (EC_AA32_SVC << ARM_EL_EC_SHIFT) | (imm16 & 0xffff) - | (is_16bit ? 0 : ARM_EL_IL); -} - -static inline uint32_t syn_aa32_hvc(uint32_t imm16) -{ - return (EC_AA32_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); -} - -static inline uint32_t syn_aa32_smc(void) -{ - return (EC_AA32_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL; -} - -static inline uint32_t syn_aa64_bkpt(uint32_t imm16) -{ - return (EC_AA64_BKPT << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); -} - -static inline uint32_t syn_aa32_bkpt(uint32_t imm16, bool is_16bit) -{ - return (EC_AA32_BKPT << ARM_EL_EC_SHIFT) | (imm16 & 0xffff) - | (is_16bit ? 0 : ARM_EL_IL); -} - -static inline uint32_t syn_aa64_sysregtrap(int op0, int op1, int op2, - int crn, int crm, int rt, - int isread) -{ - return (EC_SYSTEMREGISTERTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL - | (op0 << 20) | (op2 << 17) | (op1 << 14) | (crn << 10) | (rt << 5) - | (crm << 1) | isread; -} - -static inline uint32_t syn_cp14_rt_trap(int cv, int cond, int opc1, int opc2, - int crn, int crm, int rt, int isread, - bool is_16bit) -{ - return (EC_CP14RTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc2 << 17) | (opc1 << 14) - | (crn << 10) | (rt << 5) | (crm << 1) | isread; -} - -static inline uint32_t syn_cp15_rt_trap(int cv, int cond, int opc1, int opc2, - int crn, int crm, int rt, int isread, - bool is_16bit) -{ - return (EC_CP15RTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc2 << 17) | (opc1 << 14) - | (crn << 10) | (rt << 5) | (crm << 1) | isread; -} - -static inline uint32_t syn_cp14_rrt_trap(int cv, int cond, int opc1, int crm, - int rt, int rt2, int isread, - bool is_16bit) -{ - return (EC_CP14RRTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc1 << 16) - | (rt2 << 10) | (rt << 5) | (crm << 1) | isread; -} - -static inline uint32_t syn_cp15_rrt_trap(int cv, int cond, int opc1, int crm, - int rt, int rt2, int isread, - bool is_16bit) -{ - return (EC_CP15RRTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc1 << 16) - | (rt2 << 10) | (rt << 5) | (crm << 1) | isread; -} - -static inline uint32_t syn_fp_access_trap(int cv, int cond, bool is_16bit) -{ - /* AArch32 FP trap or any AArch64 FP/SIMD trap: TA == 0 coproc == 0xa */ - return (EC_ADVSIMDFPACCESSTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | 0xa; -} - -static inline uint32_t syn_simd_access_trap(int cv, int cond, bool is_16bit) -{ - /* AArch32 SIMD trap: TA == 1 coproc == 0 */ - return (EC_ADVSIMDFPACCESSTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (1 << 5); -} - -static inline uint32_t syn_sve_access_trap(void) -{ - return EC_SVEACCESSTRAP << ARM_EL_EC_SHIFT; -} - -static inline uint32_t syn_pactrap(void) -{ - return EC_PACTRAP << ARM_EL_EC_SHIFT; -} - -static inline uint32_t syn_btitrap(int btype) -{ - return (EC_BTITRAP << ARM_EL_EC_SHIFT) | btype; -} - -static inline uint32_t syn_insn_abort(int same_el, int ea, int s1ptw, int fsc) -{ - return (EC_INSNABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | (ea << 9) | (s1ptw << 7) | fsc; -} - -static inline uint32_t syn_data_abort_no_iss(int same_el, int fnv, - int ea, int cm, int s1ptw, - int wnr, int fsc) -{ - return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL - | (fnv << 10) | (ea << 9) | (cm << 8) | (s1ptw << 7) - | (wnr << 6) | fsc; -} - -static inline uint32_t syn_data_abort_with_iss(int same_el, - int sas, int sse, int srt, - int sf, int ar, - int ea, int cm, int s1ptw, - int wnr, int fsc, - bool is_16bit) -{ - return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | ARM_EL_ISV | (sas << 22) | (sse << 21) | (srt << 16) - | (sf << 15) | (ar << 14) - | (ea << 9) | (cm << 8) | (s1ptw << 7) | (wnr << 6) | fsc; -} - -static inline uint32_t syn_swstep(int same_el, int isv, int ex) -{ - return (EC_SOFTWARESTEP << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | (isv << 24) | (ex << 6) | 0x22; -} - -static inline uint32_t syn_watchpoint(int same_el, int cm, int wnr) -{ - return (EC_WATCHPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | (cm << 8) | (wnr << 6) | 0x22; -} - -static inline uint32_t syn_breakpoint(int same_el) -{ - return (EC_BREAKPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | 0x22; -} - -static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) -{ - return (EC_WFX_TRAP << ARM_EL_EC_SHIFT) | - (is_16bit ? 0 : (1 << ARM_EL_IL_SHIFT)) | - (cv << 24) | (cond << 20) | ti; -} - /* Update a QEMU watchpoint based on the information the guest has set in the * DBGWCR<n>_EL1 and DBGWVR<n>_EL1 registers. */ diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ +/* + * QEMU ARM CPU -- syndrome functions and types + * + * Copyright (c) 2014 Linaro Ltd + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see + * <http://www.gnu.org/licenses/gpl-2.0.html> + * + * This header defines functions, types, etc which need to be shared + * between different source files within target/arm/ but which are + * private to it and not required by the rest of QEMU. + */ + +#ifndef TARGET_ARM_SYNDROME_H +#define TARGET_ARM_SYNDROME_H + +/* Valid Syndrome Register EC field values */ +enum arm_exception_class { + EC_UNCATEGORIZED = 0x00, + EC_WFX_TRAP = 0x01, + EC_CP15RTTRAP = 0x03, + EC_CP15RRTTRAP = 0x04, + EC_CP14RTTRAP = 0x05, + EC_CP14DTTRAP = 0x06, + EC_ADVSIMDFPACCESSTRAP = 0x07, + EC_FPIDTRAP = 0x08, + EC_PACTRAP = 0x09, + EC_CP14RRTTRAP = 0x0c, + EC_BTITRAP = 0x0d, + EC_ILLEGALSTATE = 0x0e, + EC_AA32_SVC = 0x11, + EC_AA32_HVC = 0x12, + EC_AA32_SMC = 0x13, + EC_AA64_SVC = 0x15, + EC_AA64_HVC = 0x16, + EC_AA64_SMC = 0x17, + EC_SYSTEMREGISTERTRAP = 0x18, + EC_SVEACCESSTRAP = 0x19, + EC_INSNABORT = 0x20, + EC_INSNABORT_SAME_EL = 0x21, + EC_PCALIGNMENT = 0x22, + EC_DATAABORT = 0x24, + EC_DATAABORT_SAME_EL = 0x25, + EC_SPALIGNMENT = 0x26, + EC_AA32_FPTRAP = 0x28, + EC_AA64_FPTRAP = 0x2c, + EC_SERROR = 0x2f, + EC_BREAKPOINT = 0x30, + EC_BREAKPOINT_SAME_EL = 0x31, + EC_SOFTWARESTEP = 0x32, + EC_SOFTWARESTEP_SAME_EL = 0x33, + EC_WATCHPOINT = 0x34, + EC_WATCHPOINT_SAME_EL = 0x35, + EC_AA32_BKPT = 0x38, + EC_VECTORCATCH = 0x3a, + EC_AA64_BKPT = 0x3c, +}; + +#define ARM_EL_EC_SHIFT 26 +#define ARM_EL_IL_SHIFT 25 +#define ARM_EL_ISV_SHIFT 24 +#define ARM_EL_IL (1 << ARM_EL_IL_SHIFT) +#define ARM_EL_ISV (1 << ARM_EL_ISV_SHIFT) + +static inline uint32_t syn_get_ec(uint32_t syn) +{ + return syn >> ARM_EL_EC_SHIFT; +} + +/* + * Utility functions for constructing various kinds of syndrome value. + * Note that in general we follow the AArch64 syndrome values; in a + * few cases the value in HSR for exceptions taken to AArch32 Hyp + * mode differs slightly, and we fix this up when populating HSR in + * arm_cpu_do_interrupt_aarch32_hyp(). + * The exception is FP/SIMD access traps -- these report extra information + * when taking an exception to AArch32. For those we include the extra coproc + * and TA fields, and mask them out when taking the exception to AArch64. + */ +static inline uint32_t syn_uncategorized(void) +{ + return (EC_UNCATEGORIZED << ARM_EL_EC_SHIFT) | ARM_EL_IL; +} + +static inline uint32_t syn_aa64_svc(uint32_t imm16) +{ + return (EC_AA64_SVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); +} + +static inline uint32_t syn_aa64_hvc(uint32_t imm16) +{ + return (EC_AA64_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); +} + +static inline uint32_t syn_aa64_smc(uint32_t imm16) +{ + return (EC_AA64_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); +} + +static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_16bit) +{ + return (EC_AA32_SVC << ARM_EL_EC_SHIFT) | (imm16 & 0xffff) + | (is_16bit ? 0 : ARM_EL_IL); +} + +static inline uint32_t syn_aa32_hvc(uint32_t imm16) +{ + return (EC_AA32_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); +} + +static inline uint32_t syn_aa32_smc(void) +{ + return (EC_AA32_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL; +} + +static inline uint32_t syn_aa64_bkpt(uint32_t imm16) +{ + return (EC_AA64_BKPT << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); +} + +static inline uint32_t syn_aa32_bkpt(uint32_t imm16, bool is_16bit) +{ + return (EC_AA32_BKPT << ARM_EL_EC_SHIFT) | (imm16 & 0xffff) + | (is_16bit ? 0 : ARM_EL_IL); +} + +static inline uint32_t syn_aa64_sysregtrap(int op0, int op1, int op2, + int crn, int crm, int rt, + int isread) +{ + return (EC_SYSTEMREGISTERTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL + | (op0 << 20) | (op2 << 17) | (op1 << 14) | (crn << 10) | (rt << 5) + | (crm << 1) | isread; +} + +static inline uint32_t syn_cp14_rt_trap(int cv, int cond, int opc1, int opc2, + int crn, int crm, int rt, int isread, + bool is_16bit) +{ + return (EC_CP14RTTRAP << ARM_EL_EC_SHIFT) + | (is_16bit ? 0 : ARM_EL_IL) + | (cv << 24) | (cond << 20) | (opc2 << 17) | (opc1 << 14) + | (crn << 10) | (rt << 5) | (crm << 1) | isread; +} + +static inline uint32_t syn_cp15_rt_trap(int cv, int cond, int opc1, int opc2, + int crn, int crm, int rt, int isread, + bool is_16bit) +{ + return (EC_CP15RTTRAP << ARM_EL_EC_SHIFT) + | (is_16bit ? 0 : ARM_EL_IL) + | (cv << 24) | (cond << 20) | (opc2 << 17) | (opc1 << 14) + | (crn << 10) | (rt << 5) | (crm << 1) | isread; +} + +static inline uint32_t syn_cp14_rrt_trap(int cv, int cond, int opc1, int crm, + int rt, int rt2, int isread, + bool is_16bit) +{ + return (EC_CP14RRTTRAP << ARM_EL_EC_SHIFT) + | (is_16bit ? 0 : ARM_EL_IL) + | (cv << 24) | (cond << 20) | (opc1 << 16) + | (rt2 << 10) | (rt << 5) | (crm << 1) | isread; +} + +static inline uint32_t syn_cp15_rrt_trap(int cv, int cond, int opc1, int crm, + int rt, int rt2, int isread, + bool is_16bit) +{ + return (EC_CP15RRTTRAP << ARM_EL_EC_SHIFT) + | (is_16bit ? 0 : ARM_EL_IL) + | (cv << 24) | (cond << 20) | (opc1 << 16) + | (rt2 << 10) | (rt << 5) | (crm << 1) | isread; +} + +static inline uint32_t syn_fp_access_trap(int cv, int cond, bool is_16bit) +{ + /* AArch32 FP trap or any AArch64 FP/SIMD trap: TA == 0 coproc == 0xa */ + return (EC_ADVSIMDFPACCESSTRAP << ARM_EL_EC_SHIFT) + | (is_16bit ? 0 : ARM_EL_IL) + | (cv << 24) | (cond << 20) | 0xa; +} + +static inline uint32_t syn_simd_access_trap(int cv, int cond, bool is_16bit) +{ + /* AArch32 SIMD trap: TA == 1 coproc == 0 */ + return (EC_ADVSIMDFPACCESSTRAP << ARM_EL_EC_SHIFT) + | (is_16bit ? 0 : ARM_EL_IL) + | (cv << 24) | (cond << 20) | (1 << 5); +} + +static inline uint32_t syn_sve_access_trap(void) +{ + return EC_SVEACCESSTRAP << ARM_EL_EC_SHIFT; +} + +static inline uint32_t syn_pactrap(void) +{ + return EC_PACTRAP << ARM_EL_EC_SHIFT; +} + +static inline uint32_t syn_btitrap(int btype) +{ + return (EC_BTITRAP << ARM_EL_EC_SHIFT) | btype; +} + +static inline uint32_t syn_insn_abort(int same_el, int ea, int s1ptw, int fsc) +{ + return (EC_INSNABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) + | ARM_EL_IL | (ea << 9) | (s1ptw << 7) | fsc; +} + +static inline uint32_t syn_data_abort_no_iss(int same_el, int fnv, + int ea, int cm, int s1ptw, + int wnr, int fsc) +{ + return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) + | ARM_EL_IL + | (fnv << 10) | (ea << 9) | (cm << 8) | (s1ptw << 7) + | (wnr << 6) | fsc; +} + +static inline uint32_t syn_data_abort_with_iss(int same_el, + int sas, int sse, int srt, + int sf, int ar, + int ea, int cm, int s1ptw, + int wnr, int fsc, + bool is_16bit) +{ + return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) + | (is_16bit ? 0 : ARM_EL_IL) + | ARM_EL_ISV | (sas << 22) | (sse << 21) | (srt << 16) + | (sf << 15) | (ar << 14) + | (ea << 9) | (cm << 8) | (s1ptw << 7) | (wnr << 6) | fsc; +} + +static inline uint32_t syn_swstep(int same_el, int isv, int ex) +{ + return (EC_SOFTWARESTEP << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) + | ARM_EL_IL | (isv << 24) | (ex << 6) | 0x22; +} + +static inline uint32_t syn_watchpoint(int same_el, int cm, int wnr) +{ + return (EC_WATCHPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) + | ARM_EL_IL | (cm << 8) | (wnr << 6) | 0x22; +} + +static inline uint32_t syn_breakpoint(int same_el) +{ + return (EC_BREAKPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) + | ARM_EL_IL | 0x22; +} + +static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) +{ + return (EC_WFX_TRAP << ARM_EL_EC_SHIFT) | + (is_16bit ? 0 : (1 << ARM_EL_IL_SHIFT)) | + (cv << 24) | (cond << 20) | ti; +} + +#endif /* TARGET_ARM_SYNDROME_H */ -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> A proper syndrome is required to fill in the proper si_code. Use page_get_flags to determine permission vs translation for user-only. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-27-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/aarch64/cpu_loop.c | 24 +++++++++++++++++++++--- target/arm/tlb_helper.c | 15 +++++++++------ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/cpu_loop.c +++ b/linux-user/aarch64/cpu_loop.c @@ -XXX,XX +XXX,XX @@ #include "cpu_loop-common.h" #include "qemu/guest-random.h" #include "hw/semihosting/common-semi.h" +#include "target/arm/syndrome.h" #define get_user_code_u32(x, gaddr, env) \ ({ abi_long __r = get_user_u32((x), (gaddr)); \ @@ -XXX,XX +XXX,XX @@ void cpu_loop(CPUARMState *env) { CPUState *cs = env_cpu(env); - int trapnr; + int trapnr, ec, fsc; abi_long ret; target_siginfo_t info; @@ -XXX,XX +XXX,XX @@ void cpu_loop(CPUARMState *env) case EXCP_DATA_ABORT: info.si_signo = TARGET_SIGSEGV; info.si_errno = 0; - /* XXX: check env->error_code */ - info.si_code = TARGET_SEGV_MAPERR; info._sifields._sigfault._addr = env->exception.vaddress; + + /* We should only arrive here with EC in {DATAABORT, INSNABORT}. */ + ec = syn_get_ec(env->exception.syndrome); + assert(ec == EC_DATAABORT || ec == EC_INSNABORT); + + /* Both EC have the same format for FSC, or close enough. */ + fsc = extract32(env->exception.syndrome, 0, 6); + switch (fsc) { + case 0x04 ... 0x07: /* Translation fault, level {0-3} */ + info.si_code = TARGET_SEGV_MAPERR; + break; + case 0x09 ... 0x0b: /* Access flag fault, level {1-3} */ + case 0x0d ... 0x0f: /* Permission fault, level {1-3} */ + info.si_code = TARGET_SEGV_ACCERR; + break; + default: + g_assert_not_reached(); + } + queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info); break; case EXCP_DEBUG: diff --git a/target/arm/tlb_helper.c b/target/arm/tlb_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tlb_helper.c +++ b/target/arm/tlb_helper.c @@ -XXX,XX +XXX,XX @@ bool arm_cpu_tlb_fill(CPUState *cs, vaddr address, int size, bool probe, uintptr_t retaddr) { ARMCPU *cpu = ARM_CPU(cs); + ARMMMUFaultInfo fi = {}; #ifdef CONFIG_USER_ONLY - cpu->env.exception.vaddress = address; - if (access_type == MMU_INST_FETCH) { - cs->exception_index = EXCP_PREFETCH_ABORT; + int flags = page_get_flags(useronly_clean_ptr(address)); + if (flags & PAGE_VALID) { + fi.type = ARMFault_Permission; } else { - cs->exception_index = EXCP_DATA_ABORT; + fi.type = ARMFault_Translation; } - cpu_loop_exit_restore(cs, retaddr); + + /* now we have a real cpu fault */ + cpu_restore_state(cs, retaddr, true); + arm_deliver_fault(cpu, address, access_type, mmu_idx, &fi); #else hwaddr phys_addr; target_ulong page_size; int prot, ret; MemTxAttrs attrs = {}; - ARMMMUFaultInfo fi = {}; ARMCacheAttrs cacheattrs = {}; /* -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-28-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/aarch64/target_signal.h | 2 ++ linux-user/aarch64/cpu_loop.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/linux-user/aarch64/target_signal.h b/linux-user/aarch64/target_signal.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/target_signal.h +++ b/linux-user/aarch64/target_signal.h @@ -XXX,XX +XXX,XX @@ typedef struct target_sigaltstack { #include "../generic/signal.h" +#define TARGET_SEGV_MTESERR 9 /* Synchronous ARM MTE exception */ + #define TARGET_ARCH_HAS_SETUP_FRAME #endif /* AARCH64_TARGET_SIGNAL_H */ diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/cpu_loop.c +++ b/linux-user/aarch64/cpu_loop.c @@ -XXX,XX +XXX,XX @@ void cpu_loop(CPUARMState *env) case 0x0d ... 0x0f: /* Permission fault, level {1-3} */ info.si_code = TARGET_SEGV_ACCERR; break; + case 0x11: /* Synchronous Tag Check Fault */ + info.si_code = TARGET_SEGV_MTESERR; + break; default: g_assert_not_reached(); } -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> The real kernel collects _TIF_MTE_ASYNC_FAULT into the current thread's state on any kernel entry (interrupt, exception etc), and then delivers the signal in advance of resuming the thread. This means that while the signal won't be delivered immediately, it will not be delayed forever -- at minimum it will be delivered after the next clock interrupt. We don't have a clock interrupt in linux-user, so we issue a cpu_kick to signal a return to the main loop at the end of the current TB. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-29-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- linux-user/aarch64/target_signal.h | 1 + linux-user/aarch64/cpu_loop.c | 11 +++++++++++ target/arm/mte_helper.c | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/linux-user/aarch64/target_signal.h b/linux-user/aarch64/target_signal.h index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/target_signal.h +++ b/linux-user/aarch64/target_signal.h @@ -XXX,XX +XXX,XX @@ typedef struct target_sigaltstack { #include "../generic/signal.h" +#define TARGET_SEGV_MTEAERR 8 /* Asynchronous ARM MTE error */ #define TARGET_SEGV_MTESERR 9 /* Synchronous ARM MTE exception */ #define TARGET_ARCH_HAS_SETUP_FRAME diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c index XXXXXXX..XXXXXXX 100644 --- a/linux-user/aarch64/cpu_loop.c +++ b/linux-user/aarch64/cpu_loop.c @@ -XXX,XX +XXX,XX @@ void cpu_loop(CPUARMState *env) EXCP_DUMP(env, "qemu: unhandled CPU exception 0x%x - aborting\n", trapnr); abort(); } + + /* Check for MTE asynchronous faults */ + if (unlikely(env->cp15.tfsr_el[0])) { + env->cp15.tfsr_el[0] = 0; + info.si_signo = TARGET_SIGSEGV; + info.si_errno = 0; + info._sifields._sigfault._addr = 0; + info.si_code = TARGET_SEGV_MTEAERR; + queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info); + } + process_pending_signals(env); /* Exception return on AArch64 always clears the exclusive monitor, * so any return to running guest code implies this. diff --git a/target/arm/mte_helper.c b/target/arm/mte_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/mte_helper.c +++ b/target/arm/mte_helper.c @@ -XXX,XX +XXX,XX @@ static void mte_check_fail(CPUARMState *env, uint32_t desc, select = 0; } env->cp15.tfsr_el[el] |= 1 << select; +#ifdef CONFIG_USER_ONLY + /* + * Stand in for a timer irq, setting _TIF_MTE_ASYNC_FAULT, + * which then sends a SIGSEGV when the thread is next scheduled. + * This cpu will return to the main loop at the end of the TB, + * which is rather sooner than "normal". But the alternative + * is waiting until the next syscall. + */ + qemu_cpu_kick(env_cpu(env)); +#endif break; default: -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Use the now-saved PAGE_ANON and PAGE_MTE bits, and the per-page saved data. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-30-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/mte_helper.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/target/arm/mte_helper.c b/target/arm/mte_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/mte_helper.c +++ b/target/arm/mte_helper.c @@ -XXX,XX +XXX,XX @@ static uint8_t *allocation_tag_mem(CPUARMState *env, int ptr_mmu_idx, int tag_size, uintptr_t ra) { #ifdef CONFIG_USER_ONLY - /* Tag storage not implemented. */ - return NULL; + uint64_t clean_ptr = useronly_clean_ptr(ptr); + int flags = page_get_flags(clean_ptr); + uint8_t *tags; + uintptr_t index; + + if (!(flags & (ptr_access == MMU_DATA_STORE ? PAGE_WRITE : PAGE_READ))) { + /* SIGSEGV */ + arm_cpu_tlb_fill(env_cpu(env), ptr, ptr_size, ptr_access, + ptr_mmu_idx, false, ra); + g_assert_not_reached(); + } + + /* Require both MAP_ANON and PROT_MTE for the page. */ + if (!(flags & PAGE_ANON) || !(flags & PAGE_MTE)) { + return NULL; + } + + tags = page_get_target_data(clean_ptr); + if (tags == NULL) { + size_t alloc_size = TARGET_PAGE_SIZE >> (LOG2_TAG_GRANULE + 1); + tags = page_alloc_target_data(clean_ptr, alloc_size); + assert(tags != NULL); + } + + index = extract32(ptr, LOG2_TAG_GRANULE + 1, + TARGET_PAGE_BITS - LOG2_TAG_GRANULE - 1); + return tags + index; #else uintptr_t index; CPUIOTLBEntry *iotlbentry; -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-31-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/cpu.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ static void arm_cpu_reset(DeviceState *dev) * Note that this must match useronly_clean_ptr. */ env->cp15.tcr_el[1].raw_tcr = (1ULL << 37); + + /* Enable MTE */ + if (cpu_isar_feature(aa64_mte, cpu)) { + /* Enable tag access, but leave TCF0 as No Effect (0). */ + env->cp15.sctlr_el[1] |= SCTLR_ATA0; + /* + * Exclude all tags, so that tag 0 is always used. + * This corresponds to Linux current->thread.gcr_incl = 0. + * + * Set RRND, so that helper_irg() will generate a seed later. + * Here in cpu_reset(), the crypto subsystem has not yet been + * initialized. + */ + env->cp15.gcr_el1 = 0x1ffff; + } #else /* Reset into the highest available EL */ if (arm_feature(env, ARM_FEATURE_EL3)) { -- 2.20.1
From: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20210212184902.1251044-32-richard.henderson@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- tests/tcg/aarch64/mte.h | 60 +++++++++++++++++++++++++++++++ tests/tcg/aarch64/mte-1.c | 28 +++++++++++++++ tests/tcg/aarch64/mte-2.c | 45 +++++++++++++++++++++++ tests/tcg/aarch64/mte-3.c | 51 ++++++++++++++++++++++++++ tests/tcg/aarch64/mte-4.c | 45 +++++++++++++++++++++++ tests/tcg/aarch64/Makefile.target | 6 ++++ tests/tcg/configure.sh | 4 +++ 7 files changed, 239 insertions(+) create mode 100644 tests/tcg/aarch64/mte.h create mode 100644 tests/tcg/aarch64/mte-1.c create mode 100644 tests/tcg/aarch64/mte-2.c create mode 100644 tests/tcg/aarch64/mte-3.c create mode 100644 tests/tcg/aarch64/mte-4.c diff --git a/tests/tcg/aarch64/mte.h b/tests/tcg/aarch64/mte.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/mte.h @@ -XXX,XX +XXX,XX @@ +/* + * Linux kernel fallback API definitions for MTE and test helpers. + * + * Copyright (c) 2021 Linaro Ltd + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <signal.h> +#include <sys/mman.h> +#include <sys/prctl.h> + +#ifndef PR_SET_TAGGED_ADDR_CTRL +# define PR_SET_TAGGED_ADDR_CTRL 55 +#endif +#ifndef PR_TAGGED_ADDR_ENABLE +# define PR_TAGGED_ADDR_ENABLE (1UL << 0) +#endif +#ifndef PR_MTE_TCF_SHIFT +# define PR_MTE_TCF_SHIFT 1 +# define PR_MTE_TCF_NONE (0UL << PR_MTE_TCF_SHIFT) +# define PR_MTE_TCF_SYNC (1UL << PR_MTE_TCF_SHIFT) +# define PR_MTE_TCF_ASYNC (2UL << PR_MTE_TCF_SHIFT) +# define PR_MTE_TAG_SHIFT 3 +#endif + +#ifndef PROT_MTE +# define PROT_MTE 0x20 +#endif + +#ifndef SEGV_MTEAERR +# define SEGV_MTEAERR 8 +# define SEGV_MTESERR 9 +#endif + +static void enable_mte(int tcf) +{ + int r = prctl(PR_SET_TAGGED_ADDR_CTRL, + PR_TAGGED_ADDR_ENABLE | tcf | (0xfffe << PR_MTE_TAG_SHIFT), + 0, 0, 0); + if (r < 0) { + perror("PR_SET_TAGGED_ADDR_CTRL"); + exit(2); + } +} + +static void *alloc_mte_mem(size_t size) +{ + void *p = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_MTE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) { + perror("mmap PROT_MTE"); + exit(2); + } + return p; +} diff --git a/tests/tcg/aarch64/mte-1.c b/tests/tcg/aarch64/mte-1.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/mte-1.c @@ -XXX,XX +XXX,XX @@ +/* + * Memory tagging, basic pass cases. + * + * Copyright (c) 2021 Linaro Ltd + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "mte.h" + +int main(int ac, char **av) +{ + int *p0, *p1, *p2; + long c; + + enable_mte(PR_MTE_TCF_NONE); + p0 = alloc_mte_mem(sizeof(*p0)); + + asm("irg %0,%1,%2" : "=r"(p1) : "r"(p0), "r"(1)); + assert(p1 != p0); + asm("subp %0,%1,%2" : "=r"(c) : "r"(p0), "r"(p1)); + assert(c == 0); + + asm("stg %0, [%0]" : : "r"(p1)); + asm("ldg %0, [%1]" : "=r"(p2) : "r"(p0), "0"(p0)); + assert(p1 == p2); + + return 0; +} diff --git a/tests/tcg/aarch64/mte-2.c b/tests/tcg/aarch64/mte-2.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/mte-2.c @@ -XXX,XX +XXX,XX @@ +/* + * Memory tagging, basic fail cases, synchronous signals. + * + * Copyright (c) 2021 Linaro Ltd + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "mte.h" + +void pass(int sig, siginfo_t *info, void *uc) +{ + assert(info->si_code == SEGV_MTESERR); + exit(0); +} + +int main(int ac, char **av) +{ + struct sigaction sa; + int *p0, *p1, *p2; + long excl = 1; + + enable_mte(PR_MTE_TCF_SYNC); + p0 = alloc_mte_mem(sizeof(*p0)); + + /* Create two differently tagged pointers. */ + asm("irg %0,%1,%2" : "=r"(p1) : "r"(p0), "r"(excl)); + asm("gmi %0,%1,%0" : "+r"(excl) : "r" (p1)); + assert(excl != 1); + asm("irg %0,%1,%2" : "=r"(p2) : "r"(p0), "r"(excl)); + assert(p1 != p2); + + /* Store the tag from the first pointer. */ + asm("stg %0, [%0]" : : "r"(p1)); + + *p1 = 0; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = pass; + sa.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sa, NULL); + + *p2 = 0; + + abort(); +} diff --git a/tests/tcg/aarch64/mte-3.c b/tests/tcg/aarch64/mte-3.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/mte-3.c @@ -XXX,XX +XXX,XX @@ +/* + * Memory tagging, basic fail cases, asynchronous signals. + * + * Copyright (c) 2021 Linaro Ltd + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "mte.h" + +void pass(int sig, siginfo_t *info, void *uc) +{ + assert(info->si_code == SEGV_MTEAERR); + exit(0); +} + +int main(int ac, char **av) +{ + struct sigaction sa; + long *p0, *p1, *p2; + long excl = 1; + + enable_mte(PR_MTE_TCF_ASYNC); + p0 = alloc_mte_mem(sizeof(*p0)); + + /* Create two differently tagged pointers. */ + asm("irg %0,%1,%2" : "=r"(p1) : "r"(p0), "r"(excl)); + asm("gmi %0,%1,%0" : "+r"(excl) : "r" (p1)); + assert(excl != 1); + asm("irg %0,%1,%2" : "=r"(p2) : "r"(p0), "r"(excl)); + assert(p1 != p2); + + /* Store the tag from the first pointer. */ + asm("stg %0, [%0]" : : "r"(p1)); + + *p1 = 0; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = pass; + sa.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sa, NULL); + + /* + * Signal for async error will happen eventually. + * For a real kernel this should be after the next IRQ (e.g. timer). + * For qemu linux-user, we kick the cpu and exit at the next TB. + * In either case, loop until this happens (or killed by timeout). + * For extra sauce, yield, producing EXCP_YIELD to cpu_loop(). + */ + asm("str %0, [%0]; yield" : : "r"(p2)); + while (1); +} diff --git a/tests/tcg/aarch64/mte-4.c b/tests/tcg/aarch64/mte-4.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/tcg/aarch64/mte-4.c @@ -XXX,XX +XXX,XX @@ +/* + * Memory tagging, re-reading tag checks. + * + * Copyright (c) 2021 Linaro Ltd + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "mte.h" + +void __attribute__((noinline)) tagset(void *p, size_t size) +{ + size_t i; + for (i = 0; i < size; i += 16) { + asm("stg %0, [%0]" : : "r"(p + i)); + } +} + +void __attribute__((noinline)) tagcheck(void *p, size_t size) +{ + size_t i; + void *c; + + for (i = 0; i < size; i += 16) { + asm("ldg %0, [%1]" : "=r"(c) : "r"(p + i), "0"(p)); + assert(c == p); + } +} + +int main(int ac, char **av) +{ + size_t size = getpagesize() * 4; + long excl = 1; + int *p0, *p1; + + enable_mte(PR_MTE_TCF_ASYNC); + p0 = alloc_mte_mem(size); + + /* Tag the pointer. */ + asm("irg %0,%1,%2" : "=r"(p1) : "r"(p0), "r"(excl)); + + tagset(p1, size); + tagcheck(p1, size); + + return 0; +} diff --git a/tests/tcg/aarch64/Makefile.target b/tests/tcg/aarch64/Makefile.target index XXXXXXX..XXXXXXX 100644 --- a/tests/tcg/aarch64/Makefile.target +++ b/tests/tcg/aarch64/Makefile.target @@ -XXX,XX +XXX,XX @@ endif # bti-2 tests PROT_BTI, so no special compiler support required. AARCH64_TESTS += bti-2 +# MTE Tests +ifneq ($(DOCKER_IMAGE)$(CROSS_CC_HAS_ARMV8_MTE),) +AARCH64_TESTS += mte-1 mte-2 mte-3 mte-4 +mte-%: CFLAGS += -march=armv8.5-a+memtag +endif + # Semihosting smoke test for linux-user AARCH64_TESTS += semihosting run-semihosting: semihosting diff --git a/tests/tcg/configure.sh b/tests/tcg/configure.sh index XXXXXXX..XXXXXXX 100755 --- a/tests/tcg/configure.sh +++ b/tests/tcg/configure.sh @@ -XXX,XX +XXX,XX @@ for target in $target_list; do -mbranch-protection=standard -o $TMPE $TMPC; then echo "CROSS_CC_HAS_ARMV8_BTI=y" >> $config_target_mak fi + if do_compiler "$target_compiler" $target_compiler_cflags \ + -march=armv8.5-a+memtag -o $TMPE $TMPC; then + echo "CROSS_CC_HAS_ARMV8_MTE=y" >> $config_target_mak + fi ;; esac -- 2.20.1
From: Hao Wu <wuhaotsh@google.com> This commit implements the single-byte mode of the SMBus. Each Nuvoton SoC has 16 System Management Bus (SMBus). These buses compliant with SMBus and I2C protocol. This patch implements the single-byte mode of the SMBus. In this mode, the user sends or receives a byte each time. The SMBus device transmits it to the underlying i2c device and sends an interrupt back to the QEMU guest. Reviewed-by: Doug Evans<dje@google.com> Reviewed-by: Tyrong Ting<kfting@nuvoton.com> Signed-off-by: Hao Wu <wuhaotsh@google.com> Reviewed-by: Corey Minyard <cminyard@mvista.com> Message-id: 20210210220426.3577804-2-wuhaotsh@google.com Acked-by: Corey Minyard <cminyard@mvista.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- docs/system/arm/nuvoton.rst | 2 +- include/hw/arm/npcm7xx.h | 2 + include/hw/i2c/npcm7xx_smbus.h | 88 ++++ hw/arm/npcm7xx.c | 68 ++- hw/i2c/npcm7xx_smbus.c | 783 +++++++++++++++++++++++++++++++++ hw/i2c/meson.build | 1 + hw/i2c/trace-events | 11 + 7 files changed, 938 insertions(+), 17 deletions(-) create mode 100644 include/hw/i2c/npcm7xx_smbus.h create mode 100644 hw/i2c/npcm7xx_smbus.c diff --git a/docs/system/arm/nuvoton.rst b/docs/system/arm/nuvoton.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/nuvoton.rst +++ b/docs/system/arm/nuvoton.rst @@ -XXX,XX +XXX,XX @@ Supported devices * GPIO controller * Analog to Digital Converter (ADC) * Pulse Width Modulation (PWM) + * SMBus controller (SMBF) Missing devices --------------- @@ -XXX,XX +XXX,XX @@ Missing devices * Ethernet controllers (GMAC and EMC) * USB device (USBD) - * SMBus controller (SMBF) * Peripheral SPI controller (PSPI) * SD/MMC host * PECI interface diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/npcm7xx.h +++ b/include/hw/arm/npcm7xx.h @@ -XXX,XX +XXX,XX @@ #include "hw/adc/npcm7xx_adc.h" #include "hw/cpu/a9mpcore.h" #include "hw/gpio/npcm7xx_gpio.h" +#include "hw/i2c/npcm7xx_smbus.h" #include "hw/mem/npcm7xx_mc.h" #include "hw/misc/npcm7xx_clk.h" #include "hw/misc/npcm7xx_gcr.h" @@ -XXX,XX +XXX,XX @@ typedef struct NPCM7xxState { NPCM7xxMCState mc; NPCM7xxRNGState rng; NPCM7xxGPIOState gpio[8]; + NPCM7xxSMBusState smbus[16]; EHCISysBusState ehci; OHCISysBusState ohci; NPCM7xxFIUState fiu[2]; diff --git a/include/hw/i2c/npcm7xx_smbus.h b/include/hw/i2c/npcm7xx_smbus.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/i2c/npcm7xx_smbus.h @@ -XXX,XX +XXX,XX @@ +/* + * Nuvoton NPCM7xx SMBus Module. + * + * 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. + */ +#ifndef NPCM7XX_SMBUS_H +#define NPCM7XX_SMBUS_H + +#include "exec/memory.h" +#include "hw/i2c/i2c.h" +#include "hw/irq.h" +#include "hw/sysbus.h" + +/* + * Number of addresses this module contains. Do not change this without + * incrementing the version_id in the vmstate. + */ +#define NPCM7XX_SMBUS_NR_ADDRS 10 + +typedef enum NPCM7xxSMBusStatus { + NPCM7XX_SMBUS_STATUS_IDLE, + NPCM7XX_SMBUS_STATUS_SENDING, + NPCM7XX_SMBUS_STATUS_RECEIVING, + NPCM7XX_SMBUS_STATUS_NEGACK, + NPCM7XX_SMBUS_STATUS_STOPPING_LAST_RECEIVE, + NPCM7XX_SMBUS_STATUS_STOPPING_NEGACK, +} NPCM7xxSMBusStatus; + +/* + * struct NPCM7xxSMBusState - System Management Bus device state. + * @bus: The underlying I2C Bus. + * @irq: GIC interrupt line to fire on events (if enabled). + * @sda: The serial data register. + * @st: The status register. + * @cst: The control status register. + * @cst2: The control status register 2. + * @cst3: The control status register 3. + * @ctl1: The control register 1. + * @ctl2: The control register 2. + * @ctl3: The control register 3. + * @ctl4: The control register 4. + * @ctl5: The control register 5. + * @addr: The SMBus module's own addresses on the I2C bus. + * @scllt: The SCL low time register. + * @sclht: The SCL high time register. + * @status: The current status of the SMBus. + */ +typedef struct NPCM7xxSMBusState { + SysBusDevice parent; + + MemoryRegion iomem; + + I2CBus *bus; + qemu_irq irq; + + uint8_t sda; + uint8_t st; + uint8_t cst; + uint8_t cst2; + uint8_t cst3; + uint8_t ctl1; + uint8_t ctl2; + uint8_t ctl3; + uint8_t ctl4; + uint8_t ctl5; + uint8_t addr[NPCM7XX_SMBUS_NR_ADDRS]; + + uint8_t scllt; + uint8_t sclht; + + NPCM7xxSMBusStatus status; +} NPCM7xxSMBusState; + +#define TYPE_NPCM7XX_SMBUS "npcm7xx-smbus" +#define NPCM7XX_SMBUS(obj) OBJECT_CHECK(NPCM7xxSMBusState, (obj), \ + TYPE_NPCM7XX_SMBUS) + +#endif /* NPCM7XX_SMBUS_H */ diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/npcm7xx.c +++ b/hw/arm/npcm7xx.c @@ -XXX,XX +XXX,XX @@ enum NPCM7xxInterrupt { NPCM7XX_WDG2_IRQ, /* Timer Module 2 Watchdog */ NPCM7XX_EHCI_IRQ = 61, NPCM7XX_OHCI_IRQ = 62, + NPCM7XX_SMBUS0_IRQ = 64, + NPCM7XX_SMBUS1_IRQ, + NPCM7XX_SMBUS2_IRQ, + NPCM7XX_SMBUS3_IRQ, + NPCM7XX_SMBUS4_IRQ, + NPCM7XX_SMBUS5_IRQ, + NPCM7XX_SMBUS6_IRQ, + NPCM7XX_SMBUS7_IRQ, + NPCM7XX_SMBUS8_IRQ, + NPCM7XX_SMBUS9_IRQ, + NPCM7XX_SMBUS10_IRQ, + NPCM7XX_SMBUS11_IRQ, + NPCM7XX_SMBUS12_IRQ, + NPCM7XX_SMBUS13_IRQ, + NPCM7XX_SMBUS14_IRQ, + NPCM7XX_SMBUS15_IRQ, NPCM7XX_PWM0_IRQ = 93, /* PWM module 0 */ NPCM7XX_PWM1_IRQ, /* PWM module 1 */ NPCM7XX_GPIO0_IRQ = 116, @@ -XXX,XX +XXX,XX @@ static const hwaddr npcm7xx_pwm_addr[] = { 0xf0104000, }; +/* Direct memory-mapped access to each SMBus Module. */ +static const hwaddr npcm7xx_smbus_addr[] = { + 0xf0080000, + 0xf0081000, + 0xf0082000, + 0xf0083000, + 0xf0084000, + 0xf0085000, + 0xf0086000, + 0xf0087000, + 0xf0088000, + 0xf0089000, + 0xf008a000, + 0xf008b000, + 0xf008c000, + 0xf008d000, + 0xf008e000, + 0xf008f000, +}; + static const struct { hwaddr regs_addr; uint32_t unconnected_pins; @@ -XXX,XX +XXX,XX @@ static void npcm7xx_init(Object *obj) object_initialize_child(obj, "gpio[*]", &s->gpio[i], TYPE_NPCM7XX_GPIO); } + for (i = 0; i < ARRAY_SIZE(s->smbus); i++) { + object_initialize_child(obj, "smbus[*]", &s->smbus[i], + TYPE_NPCM7XX_SMBUS); + } + object_initialize_child(obj, "ehci", &s->ehci, TYPE_NPCM7XX_EHCI); object_initialize_child(obj, "ohci", &s->ohci, TYPE_SYSBUS_OHCI); @@ -XXX,XX +XXX,XX @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) npcm7xx_irq(s, NPCM7XX_GPIO0_IRQ + i)); } + /* SMBus modules. Cannot fail. */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_smbus_addr) != ARRAY_SIZE(s->smbus)); + for (i = 0; i < ARRAY_SIZE(s->smbus); i++) { + Object *obj = OBJECT(&s->smbus[i]); + + sysbus_realize(SYS_BUS_DEVICE(obj), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(obj), 0, npcm7xx_smbus_addr[i]); + sysbus_connect_irq(SYS_BUS_DEVICE(obj), 0, + npcm7xx_irq(s, NPCM7XX_SMBUS0_IRQ + i)); + } + /* USB Host */ object_property_set_bool(OBJECT(&s->ehci), "companion-enable", true, &error_abort); @@ -XXX,XX +XXX,XX @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) create_unimplemented_device("npcm7xx.pcierc", 0xe1000000, 64 * KiB); create_unimplemented_device("npcm7xx.kcs", 0xf0007000, 4 * KiB); create_unimplemented_device("npcm7xx.gfxi", 0xf000e000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[0]", 0xf0080000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[1]", 0xf0081000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[2]", 0xf0082000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[3]", 0xf0083000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[4]", 0xf0084000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[5]", 0xf0085000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[6]", 0xf0086000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[7]", 0xf0087000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[8]", 0xf0088000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[9]", 0xf0089000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[10]", 0xf008a000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[11]", 0xf008b000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[12]", 0xf008c000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[13]", 0xf008d000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[14]", 0xf008e000, 4 * KiB); - create_unimplemented_device("npcm7xx.smbus[15]", 0xf008f000, 4 * KiB); create_unimplemented_device("npcm7xx.espi", 0xf009f000, 4 * KiB); create_unimplemented_device("npcm7xx.peci", 0xf0100000, 4 * KiB); create_unimplemented_device("npcm7xx.siox[1]", 0xf0101000, 4 * KiB); diff --git a/hw/i2c/npcm7xx_smbus.c b/hw/i2c/npcm7xx_smbus.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/i2c/npcm7xx_smbus.c @@ -XXX,XX +XXX,XX @@ +/* + * Nuvoton NPCM7xx SMBus Module. + * + * 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 "hw/i2c/npcm7xx_smbus.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/guest-random.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" + +#include "trace.h" + +enum NPCM7xxSMBusCommonRegister { + NPCM7XX_SMB_SDA = 0x0, + NPCM7XX_SMB_ST = 0x2, + NPCM7XX_SMB_CST = 0x4, + NPCM7XX_SMB_CTL1 = 0x6, + NPCM7XX_SMB_ADDR1 = 0x8, + NPCM7XX_SMB_CTL2 = 0xa, + NPCM7XX_SMB_ADDR2 = 0xc, + NPCM7XX_SMB_CTL3 = 0xe, + NPCM7XX_SMB_CST2 = 0x18, + NPCM7XX_SMB_CST3 = 0x19, + NPCM7XX_SMB_VER = 0x1f, +}; + +enum NPCM7xxSMBusBank0Register { + NPCM7XX_SMB_ADDR3 = 0x10, + NPCM7XX_SMB_ADDR7 = 0x11, + NPCM7XX_SMB_ADDR4 = 0x12, + NPCM7XX_SMB_ADDR8 = 0x13, + NPCM7XX_SMB_ADDR5 = 0x14, + NPCM7XX_SMB_ADDR9 = 0x15, + NPCM7XX_SMB_ADDR6 = 0x16, + NPCM7XX_SMB_ADDR10 = 0x17, + NPCM7XX_SMB_CTL4 = 0x1a, + NPCM7XX_SMB_CTL5 = 0x1b, + NPCM7XX_SMB_SCLLT = 0x1c, + NPCM7XX_SMB_FIF_CTL = 0x1d, + NPCM7XX_SMB_SCLHT = 0x1e, +}; + +enum NPCM7xxSMBusBank1Register { + NPCM7XX_SMB_FIF_CTS = 0x10, + NPCM7XX_SMB_FAIR_PER = 0x11, + NPCM7XX_SMB_TXF_CTL = 0x12, + NPCM7XX_SMB_T_OUT = 0x14, + NPCM7XX_SMB_TXF_STS = 0x1a, + NPCM7XX_SMB_RXF_STS = 0x1c, + NPCM7XX_SMB_RXF_CTL = 0x1e, +}; + +/* ST fields */ +#define NPCM7XX_SMBST_STP BIT(7) +#define NPCM7XX_SMBST_SDAST BIT(6) +#define NPCM7XX_SMBST_BER BIT(5) +#define NPCM7XX_SMBST_NEGACK BIT(4) +#define NPCM7XX_SMBST_STASTR BIT(3) +#define NPCM7XX_SMBST_NMATCH BIT(2) +#define NPCM7XX_SMBST_MODE BIT(1) +#define NPCM7XX_SMBST_XMIT BIT(0) + +/* CST fields */ +#define NPCM7XX_SMBCST_ARPMATCH BIT(7) +#define NPCM7XX_SMBCST_MATCHAF BIT(6) +#define NPCM7XX_SMBCST_TGSCL BIT(5) +#define NPCM7XX_SMBCST_TSDA BIT(4) +#define NPCM7XX_SMBCST_GCMATCH BIT(3) +#define NPCM7XX_SMBCST_MATCH BIT(2) +#define NPCM7XX_SMBCST_BB BIT(1) +#define NPCM7XX_SMBCST_BUSY BIT(0) + +/* CST2 fields */ +#define NPCM7XX_SMBCST2_INTSTS BIT(7) +#define NPCM7XX_SMBCST2_MATCH7F BIT(6) +#define NPCM7XX_SMBCST2_MATCH6F BIT(5) +#define NPCM7XX_SMBCST2_MATCH5F BIT(4) +#define NPCM7XX_SMBCST2_MATCH4F BIT(3) +#define NPCM7XX_SMBCST2_MATCH3F BIT(2) +#define NPCM7XX_SMBCST2_MATCH2F BIT(1) +#define NPCM7XX_SMBCST2_MATCH1F BIT(0) + +/* CST3 fields */ +#define NPCM7XX_SMBCST3_EO_BUSY BIT(7) +#define NPCM7XX_SMBCST3_MATCH10F BIT(2) +#define NPCM7XX_SMBCST3_MATCH9F BIT(1) +#define NPCM7XX_SMBCST3_MATCH8F BIT(0) + +/* CTL1 fields */ +#define NPCM7XX_SMBCTL1_STASTRE BIT(7) +#define NPCM7XX_SMBCTL1_NMINTE BIT(6) +#define NPCM7XX_SMBCTL1_GCMEN BIT(5) +#define NPCM7XX_SMBCTL1_ACK BIT(4) +#define NPCM7XX_SMBCTL1_EOBINTE BIT(3) +#define NPCM7XX_SMBCTL1_INTEN BIT(2) +#define NPCM7XX_SMBCTL1_STOP BIT(1) +#define NPCM7XX_SMBCTL1_START BIT(0) + +/* CTL2 fields */ +#define NPCM7XX_SMBCTL2_SCLFRQ(rv) extract8((rv), 1, 6) +#define NPCM7XX_SMBCTL2_ENABLE BIT(0) + +/* CTL3 fields */ +#define NPCM7XX_SMBCTL3_SCL_LVL BIT(7) +#define NPCM7XX_SMBCTL3_SDA_LVL BIT(6) +#define NPCM7XX_SMBCTL3_BNK_SEL BIT(5) +#define NPCM7XX_SMBCTL3_400K_MODE BIT(4) +#define NPCM7XX_SMBCTL3_IDL_START BIT(3) +#define NPCM7XX_SMBCTL3_ARPMEN BIT(2) +#define NPCM7XX_SMBCTL3_SCLFRQ(rv) extract8((rv), 0, 2) + +/* ADDR fields */ +#define NPCM7XX_ADDR_EN BIT(7) +#define NPCM7XX_ADDR_A(rv) extract8((rv), 0, 6) + +#define KEEP_OLD_BIT(o, n, b) (((n) & (~(b))) | ((o) & (b))) +#define WRITE_ONE_CLEAR(o, n, b) ((n) & (b) ? (o) & (~(b)) : (o)) + +#define NPCM7XX_SMBUS_ENABLED(s) ((s)->ctl2 & NPCM7XX_SMBCTL2_ENABLE) + +/* VERSION fields values, read-only. */ +#define NPCM7XX_SMBUS_VERSION_NUMBER 1 +#define NPCM7XX_SMBUS_VERSION_FIFO_SUPPORTED 0 + +/* Reset values */ +#define NPCM7XX_SMB_ST_INIT_VAL 0x00 +#define NPCM7XX_SMB_CST_INIT_VAL 0x10 +#define NPCM7XX_SMB_CST2_INIT_VAL 0x00 +#define NPCM7XX_SMB_CST3_INIT_VAL 0x00 +#define NPCM7XX_SMB_CTL1_INIT_VAL 0x00 +#define NPCM7XX_SMB_CTL2_INIT_VAL 0x00 +#define NPCM7XX_SMB_CTL3_INIT_VAL 0xc0 +#define NPCM7XX_SMB_CTL4_INIT_VAL 0x07 +#define NPCM7XX_SMB_CTL5_INIT_VAL 0x00 +#define NPCM7XX_SMB_ADDR_INIT_VAL 0x00 +#define NPCM7XX_SMB_SCLLT_INIT_VAL 0x00 +#define NPCM7XX_SMB_SCLHT_INIT_VAL 0x00 + +static uint8_t npcm7xx_smbus_get_version(void) +{ + return NPCM7XX_SMBUS_VERSION_FIFO_SUPPORTED << 7 | + NPCM7XX_SMBUS_VERSION_NUMBER; +} + +static void npcm7xx_smbus_update_irq(NPCM7xxSMBusState *s) +{ + int level; + + if (s->ctl1 & NPCM7XX_SMBCTL1_INTEN) { + level = !!((s->ctl1 & NPCM7XX_SMBCTL1_NMINTE && + s->st & NPCM7XX_SMBST_NMATCH) || + (s->st & NPCM7XX_SMBST_BER) || + (s->st & NPCM7XX_SMBST_NEGACK) || + (s->st & NPCM7XX_SMBST_SDAST) || + (s->ctl1 & NPCM7XX_SMBCTL1_STASTRE && + s->st & NPCM7XX_SMBST_SDAST) || + (s->ctl1 & NPCM7XX_SMBCTL1_EOBINTE && + s->cst3 & NPCM7XX_SMBCST3_EO_BUSY)); + + if (level) { + s->cst2 |= NPCM7XX_SMBCST2_INTSTS; + } else { + s->cst2 &= ~NPCM7XX_SMBCST2_INTSTS; + } + qemu_set_irq(s->irq, level); + } +} + +static void npcm7xx_smbus_nack(NPCM7xxSMBusState *s) +{ + s->st &= ~NPCM7XX_SMBST_SDAST; + s->st |= NPCM7XX_SMBST_NEGACK; + s->status = NPCM7XX_SMBUS_STATUS_NEGACK; +} + +static void npcm7xx_smbus_send_byte(NPCM7xxSMBusState *s, uint8_t value) +{ + int rv = i2c_send(s->bus, value); + + if (rv) { + npcm7xx_smbus_nack(s); + } else { + s->st |= NPCM7XX_SMBST_SDAST; + } + trace_npcm7xx_smbus_send_byte((DEVICE(s)->canonical_path), value, !rv); + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_recv_byte(NPCM7xxSMBusState *s) +{ + s->sda = i2c_recv(s->bus); + s->st |= NPCM7XX_SMBST_SDAST; + if (s->st & NPCM7XX_SMBCTL1_ACK) { + trace_npcm7xx_smbus_nack(DEVICE(s)->canonical_path); + i2c_nack(s->bus); + s->st &= NPCM7XX_SMBCTL1_ACK; + } + trace_npcm7xx_smbus_recv_byte((DEVICE(s)->canonical_path), s->sda); + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_start(NPCM7xxSMBusState *s) +{ + /* + * We can start the bus if one of these is true: + * 1. The bus is idle (so we can request it) + * 2. We are the occupier (it's a repeated start condition.) + */ + int available = !i2c_bus_busy(s->bus) || + s->status != NPCM7XX_SMBUS_STATUS_IDLE; + + if (available) { + s->st |= NPCM7XX_SMBST_MODE | NPCM7XX_SMBST_XMIT | NPCM7XX_SMBST_SDAST; + s->cst |= NPCM7XX_SMBCST_BUSY; + } else { + s->st &= ~NPCM7XX_SMBST_MODE; + s->cst &= ~NPCM7XX_SMBCST_BUSY; + s->st |= NPCM7XX_SMBST_BER; + } + + trace_npcm7xx_smbus_start(DEVICE(s)->canonical_path, available); + s->cst |= NPCM7XX_SMBCST_BB; + s->status = NPCM7XX_SMBUS_STATUS_IDLE; + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_send_address(NPCM7xxSMBusState *s, uint8_t value) +{ + int recv; + int rv; + + recv = value & BIT(0); + rv = i2c_start_transfer(s->bus, value >> 1, recv); + trace_npcm7xx_smbus_send_address(DEVICE(s)->canonical_path, + value >> 1, recv, !rv); + if (rv) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: requesting i2c bus for 0x%02x failed: %d\n", + DEVICE(s)->canonical_path, value, rv); + /* Failed to start transfer. NACK to reject.*/ + if (recv) { + s->st &= ~NPCM7XX_SMBST_XMIT; + } else { + s->st |= NPCM7XX_SMBST_XMIT; + } + npcm7xx_smbus_nack(s); + npcm7xx_smbus_update_irq(s); + return; + } + + s->st &= ~NPCM7XX_SMBST_NEGACK; + if (recv) { + s->status = NPCM7XX_SMBUS_STATUS_RECEIVING; + s->st &= ~NPCM7XX_SMBST_XMIT; + } else { + s->status = NPCM7XX_SMBUS_STATUS_SENDING; + s->st |= NPCM7XX_SMBST_XMIT; + } + + if (s->ctl1 & NPCM7XX_SMBCTL1_STASTRE) { + s->st |= NPCM7XX_SMBST_STASTR; + if (!recv) { + s->st |= NPCM7XX_SMBST_SDAST; + } + } else if (recv) { + npcm7xx_smbus_recv_byte(s); + } + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_execute_stop(NPCM7xxSMBusState *s) +{ + i2c_end_transfer(s->bus); + s->st = 0; + s->cst = 0; + s->status = NPCM7XX_SMBUS_STATUS_IDLE; + s->cst3 |= NPCM7XX_SMBCST3_EO_BUSY; + trace_npcm7xx_smbus_stop(DEVICE(s)->canonical_path); + npcm7xx_smbus_update_irq(s); +} + + +static void npcm7xx_smbus_stop(NPCM7xxSMBusState *s) +{ + if (s->st & NPCM7XX_SMBST_MODE) { + switch (s->status) { + case NPCM7XX_SMBUS_STATUS_RECEIVING: + case NPCM7XX_SMBUS_STATUS_STOPPING_LAST_RECEIVE: + s->status = NPCM7XX_SMBUS_STATUS_STOPPING_LAST_RECEIVE; + break; + + case NPCM7XX_SMBUS_STATUS_NEGACK: + s->status = NPCM7XX_SMBUS_STATUS_STOPPING_NEGACK; + break; + + default: + npcm7xx_smbus_execute_stop(s); + break; + } + } +} + +static uint8_t npcm7xx_smbus_read_sda(NPCM7xxSMBusState *s) +{ + uint8_t value = s->sda; + + switch (s->status) { + case NPCM7XX_SMBUS_STATUS_STOPPING_LAST_RECEIVE: + npcm7xx_smbus_execute_stop(s); + break; + + case NPCM7XX_SMBUS_STATUS_RECEIVING: + npcm7xx_smbus_recv_byte(s); + break; + + default: + /* Do nothing */ + break; + } + + return value; +} + +static void npcm7xx_smbus_write_sda(NPCM7xxSMBusState *s, uint8_t value) +{ + s->sda = value; + if (s->st & NPCM7XX_SMBST_MODE) { + switch (s->status) { + case NPCM7XX_SMBUS_STATUS_IDLE: + npcm7xx_smbus_send_address(s, value); + break; + case NPCM7XX_SMBUS_STATUS_SENDING: + npcm7xx_smbus_send_byte(s, value); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to SDA in invalid status %d: %u\n", + DEVICE(s)->canonical_path, s->status, value); + break; + } + } +} + +static void npcm7xx_smbus_write_st(NPCM7xxSMBusState *s, uint8_t value) +{ + s->st = WRITE_ONE_CLEAR(s->st, value, NPCM7XX_SMBST_STP); + s->st = WRITE_ONE_CLEAR(s->st, value, NPCM7XX_SMBST_BER); + s->st = WRITE_ONE_CLEAR(s->st, value, NPCM7XX_SMBST_STASTR); + s->st = WRITE_ONE_CLEAR(s->st, value, NPCM7XX_SMBST_NMATCH); + + if (value & NPCM7XX_SMBST_NEGACK) { + s->st &= ~NPCM7XX_SMBST_NEGACK; + if (s->status == NPCM7XX_SMBUS_STATUS_STOPPING_NEGACK) { + npcm7xx_smbus_execute_stop(s); + } + } + + if (value & NPCM7XX_SMBST_STASTR && + s->status == NPCM7XX_SMBUS_STATUS_RECEIVING) { + npcm7xx_smbus_recv_byte(s); + } + + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_write_cst(NPCM7xxSMBusState *s, uint8_t value) +{ + uint8_t new_value = s->cst; + + s->cst = WRITE_ONE_CLEAR(new_value, value, NPCM7XX_SMBCST_BB); + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_write_cst3(NPCM7xxSMBusState *s, uint8_t value) +{ + s->cst3 = WRITE_ONE_CLEAR(s->cst3, value, NPCM7XX_SMBCST3_EO_BUSY); + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_write_ctl1(NPCM7xxSMBusState *s, uint8_t value) +{ + s->ctl1 = KEEP_OLD_BIT(s->ctl1, value, + NPCM7XX_SMBCTL1_START | NPCM7XX_SMBCTL1_STOP | NPCM7XX_SMBCTL1_ACK); + + if (value & NPCM7XX_SMBCTL1_START) { + npcm7xx_smbus_start(s); + } + + if (value & NPCM7XX_SMBCTL1_STOP) { + npcm7xx_smbus_stop(s); + } + + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_write_ctl2(NPCM7xxSMBusState *s, uint8_t value) +{ + s->ctl2 = value; + + if (!NPCM7XX_SMBUS_ENABLED(s)) { + /* Disable this SMBus module. */ + s->ctl1 = 0; + s->st = 0; + s->cst3 = s->cst3 & (~NPCM7XX_SMBCST3_EO_BUSY); + s->cst = 0; + } +} + +static void npcm7xx_smbus_write_ctl3(NPCM7xxSMBusState *s, uint8_t value) +{ + uint8_t old_ctl3 = s->ctl3; + + /* Write to SDA and SCL bits are ignored. */ + s->ctl3 = KEEP_OLD_BIT(old_ctl3, value, + NPCM7XX_SMBCTL3_SCL_LVL | NPCM7XX_SMBCTL3_SDA_LVL); +} + +static uint64_t npcm7xx_smbus_read(void *opaque, hwaddr offset, unsigned size) +{ + NPCM7xxSMBusState *s = opaque; + uint64_t value = 0; + uint8_t bank = s->ctl3 & NPCM7XX_SMBCTL3_BNK_SEL; + + /* The order of the registers are their order in memory. */ + switch (offset) { + case NPCM7XX_SMB_SDA: + value = npcm7xx_smbus_read_sda(s); + break; + + case NPCM7XX_SMB_ST: + value = s->st; + break; + + case NPCM7XX_SMB_CST: + value = s->cst; + break; + + case NPCM7XX_SMB_CTL1: + value = s->ctl1; + break; + + case NPCM7XX_SMB_ADDR1: + value = s->addr[0]; + break; + + case NPCM7XX_SMB_CTL2: + value = s->ctl2; + break; + + case NPCM7XX_SMB_ADDR2: + value = s->addr[1]; + break; + + case NPCM7XX_SMB_CTL3: + value = s->ctl3; + break; + + case NPCM7XX_SMB_CST2: + value = s->cst2; + break; + + case NPCM7XX_SMB_CST3: + value = s->cst3; + break; + + case NPCM7XX_SMB_VER: + value = npcm7xx_smbus_get_version(); + break; + + /* This register is either invalid or banked at this point. */ + default: + if (bank) { + /* Bank 1 */ + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + } else { + /* Bank 0 */ + switch (offset) { + case NPCM7XX_SMB_ADDR3: + value = s->addr[2]; + break; + + case NPCM7XX_SMB_ADDR7: + value = s->addr[6]; + break; + + case NPCM7XX_SMB_ADDR4: + value = s->addr[3]; + break; + + case NPCM7XX_SMB_ADDR8: + value = s->addr[7]; + break; + + case NPCM7XX_SMB_ADDR5: + value = s->addr[4]; + break; + + case NPCM7XX_SMB_ADDR9: + value = s->addr[8]; + break; + + case NPCM7XX_SMB_ADDR6: + value = s->addr[5]; + break; + + case NPCM7XX_SMB_ADDR10: + value = s->addr[9]; + break; + + case NPCM7XX_SMB_CTL4: + value = s->ctl4; + break; + + case NPCM7XX_SMB_CTL5: + value = s->ctl5; + break; + + case NPCM7XX_SMB_SCLLT: + value = s->scllt; + break; + + case NPCM7XX_SMB_SCLHT: + value = s->sclht; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + } + } + break; + } + + trace_npcm7xx_smbus_read(DEVICE(s)->canonical_path, offset, value, size); + + return value; +} + +static void npcm7xx_smbus_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + NPCM7xxSMBusState *s = opaque; + uint8_t bank = s->ctl3 & NPCM7XX_SMBCTL3_BNK_SEL; + + trace_npcm7xx_smbus_write(DEVICE(s)->canonical_path, offset, value, size); + + /* The order of the registers are their order in memory. */ + switch (offset) { + case NPCM7XX_SMB_SDA: + npcm7xx_smbus_write_sda(s, value); + break; + + case NPCM7XX_SMB_ST: + npcm7xx_smbus_write_st(s, value); + break; + + case NPCM7XX_SMB_CST: + npcm7xx_smbus_write_cst(s, value); + break; + + case NPCM7XX_SMB_CTL1: + npcm7xx_smbus_write_ctl1(s, value); + break; + + case NPCM7XX_SMB_ADDR1: + s->addr[0] = value; + break; + + case NPCM7XX_SMB_CTL2: + npcm7xx_smbus_write_ctl2(s, value); + break; + + case NPCM7XX_SMB_ADDR2: + s->addr[1] = value; + break; + + case NPCM7XX_SMB_CTL3: + npcm7xx_smbus_write_ctl3(s, value); + break; + + case NPCM7XX_SMB_CST2: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only reg: offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + + case NPCM7XX_SMB_CST3: + npcm7xx_smbus_write_cst3(s, value); + break; + + case NPCM7XX_SMB_VER: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only reg: offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + + /* This register is either invalid or banked at this point. */ + default: + if (bank) { + /* Bank 1 */ + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + } else { + /* Bank 0 */ + switch (offset) { + case NPCM7XX_SMB_ADDR3: + s->addr[2] = value; + break; + + case NPCM7XX_SMB_ADDR7: + s->addr[6] = value; + break; + + case NPCM7XX_SMB_ADDR4: + s->addr[3] = value; + break; + + case NPCM7XX_SMB_ADDR8: + s->addr[7] = value; + break; + + case NPCM7XX_SMB_ADDR5: + s->addr[4] = value; + break; + + case NPCM7XX_SMB_ADDR9: + s->addr[8] = value; + break; + + case NPCM7XX_SMB_ADDR6: + s->addr[5] = value; + break; + + case NPCM7XX_SMB_ADDR10: + s->addr[9] = value; + break; + + case NPCM7XX_SMB_CTL4: + s->ctl4 = value; + break; + + case NPCM7XX_SMB_CTL5: + s->ctl5 = value; + break; + + case NPCM7XX_SMB_SCLLT: + s->scllt = value; + break; + + case NPCM7XX_SMB_SCLHT: + s->sclht = value; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + } + } + break; + } +} + +static const MemoryRegionOps npcm7xx_smbus_ops = { + .read = npcm7xx_smbus_read, + .write = npcm7xx_smbus_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 1, + .unaligned = false, + }, +}; + +static void npcm7xx_smbus_enter_reset(Object *obj, ResetType type) +{ + NPCM7xxSMBusState *s = NPCM7XX_SMBUS(obj); + + s->st = NPCM7XX_SMB_ST_INIT_VAL; + s->cst = NPCM7XX_SMB_CST_INIT_VAL; + s->cst2 = NPCM7XX_SMB_CST2_INIT_VAL; + s->cst3 = NPCM7XX_SMB_CST3_INIT_VAL; + s->ctl1 = NPCM7XX_SMB_CTL1_INIT_VAL; + s->ctl2 = NPCM7XX_SMB_CTL2_INIT_VAL; + s->ctl3 = NPCM7XX_SMB_CTL3_INIT_VAL; + s->ctl4 = NPCM7XX_SMB_CTL4_INIT_VAL; + s->ctl5 = NPCM7XX_SMB_CTL5_INIT_VAL; + + for (int i = 0; i < NPCM7XX_SMBUS_NR_ADDRS; ++i) { + s->addr[i] = NPCM7XX_SMB_ADDR_INIT_VAL; + } + s->scllt = NPCM7XX_SMB_SCLLT_INIT_VAL; + s->sclht = NPCM7XX_SMB_SCLHT_INIT_VAL; + + s->status = NPCM7XX_SMBUS_STATUS_IDLE; +} + +static void npcm7xx_smbus_hold_reset(Object *obj) +{ + NPCM7xxSMBusState *s = NPCM7XX_SMBUS(obj); + + qemu_irq_lower(s->irq); +} + +static void npcm7xx_smbus_init(Object *obj) +{ + NPCM7xxSMBusState *s = NPCM7XX_SMBUS(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + sysbus_init_irq(sbd, &s->irq); + memory_region_init_io(&s->iomem, obj, &npcm7xx_smbus_ops, s, + "regs", 4 * KiB); + sysbus_init_mmio(sbd, &s->iomem); + + s->bus = i2c_init_bus(DEVICE(s), "i2c-bus"); + s->status = NPCM7XX_SMBUS_STATUS_IDLE; +} + +static const VMStateDescription vmstate_npcm7xx_smbus = { + .name = "npcm7xx-smbus", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(sda, NPCM7xxSMBusState), + VMSTATE_UINT8(st, NPCM7xxSMBusState), + VMSTATE_UINT8(cst, NPCM7xxSMBusState), + VMSTATE_UINT8(cst2, NPCM7xxSMBusState), + VMSTATE_UINT8(cst3, NPCM7xxSMBusState), + VMSTATE_UINT8(ctl1, NPCM7xxSMBusState), + VMSTATE_UINT8(ctl2, NPCM7xxSMBusState), + VMSTATE_UINT8(ctl3, NPCM7xxSMBusState), + VMSTATE_UINT8(ctl4, NPCM7xxSMBusState), + VMSTATE_UINT8(ctl5, NPCM7xxSMBusState), + VMSTATE_UINT8_ARRAY(addr, NPCM7xxSMBusState, NPCM7XX_SMBUS_NR_ADDRS), + VMSTATE_UINT8(scllt, NPCM7xxSMBusState), + VMSTATE_UINT8(sclht, NPCM7xxSMBusState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void npcm7xx_smbus_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx System Management Bus"; + dc->vmsd = &vmstate_npcm7xx_smbus; + rc->phases.enter = npcm7xx_smbus_enter_reset; + rc->phases.hold = npcm7xx_smbus_hold_reset; +} + +static const TypeInfo npcm7xx_smbus_types[] = { + { + .name = TYPE_NPCM7XX_SMBUS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxSMBusState), + .class_init = npcm7xx_smbus_class_init, + .instance_init = npcm7xx_smbus_init, + }, +}; +DEFINE_TYPES(npcm7xx_smbus_types); diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/i2c/meson.build +++ b/hw/i2c/meson.build @@ -XXX,XX +XXX,XX @@ i2c_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_i2c.c')) i2c_ss.add(when: 'CONFIG_IMX_I2C', if_true: files('imx_i2c.c')) i2c_ss.add(when: 'CONFIG_MPC_I2C', if_true: files('mpc_i2c.c')) i2c_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('microbit_i2c.c')) +i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c')) i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c')) i2c_ss.add(when: 'CONFIG_VERSATILE_I2C', if_true: files('versatile_i2c.c')) i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c')) diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/i2c/trace-events +++ b/hw/i2c/trace-events @@ -XXX,XX +XXX,XX @@ aspeed_i2c_bus_read(uint32_t busid, uint64_t offset, unsigned size, uint64_t val aspeed_i2c_bus_write(uint32_t busid, uint64_t offset, unsigned size, uint64_t value) "bus[%d]: To 0x%" PRIx64 " of size %u: 0x%" PRIx64 aspeed_i2c_bus_send(const char *mode, int i, int count, uint8_t byte) "%s send %d/%d 0x%02x" aspeed_i2c_bus_recv(const char *mode, int i, int count, uint8_t byte) "%s recv %d/%d 0x%02x" + +# npcm7xx_smbus.c + +npcm7xx_smbus_read(const char *id, uint64_t offset, uint64_t value, unsigned size) "%s offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u" +npcm7xx_smbus_write(const char *id, uint64_t offset, uint64_t value, unsigned size) "%s offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u" +npcm7xx_smbus_start(const char *id, int success) "%s starting, success: %d" +npcm7xx_smbus_send_address(const char *id, uint8_t addr, int recv, int success) "%s sending address: 0x%02x, recv: %d, success: %d" +npcm7xx_smbus_send_byte(const char *id, uint8_t value, int success) "%s send byte: 0x%02x, success: %d" +npcm7xx_smbus_recv_byte(const char *id, uint8_t value) "%s recv byte: 0x%02x" +npcm7xx_smbus_stop(const char *id) "%s stopping" +npcm7xx_smbus_nack(const char *id) "%s nacking" -- 2.20.1
From: Hao Wu <wuhaotsh@google.com> Add I2C temperature sensors for NPCM750 eval board. Reviewed-by: Doug Evans<dje@google.com> Reviewed-by: Tyrong Ting<kfting@nuvoton.com> Signed-off-by: Hao Wu <wuhaotsh@google.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20210210220426.3577804-3-wuhaotsh@google.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/npcm7xx_boards.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/hw/arm/npcm7xx_boards.c b/hw/arm/npcm7xx_boards.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/npcm7xx_boards.c +++ b/hw/arm/npcm7xx_boards.c @@ -XXX,XX +XXX,XX @@ static NPCM7xxState *npcm7xx_create_soc(MachineState *machine, return NPCM7XX(obj); } +static I2CBus *npcm7xx_i2c_get_bus(NPCM7xxState *soc, uint32_t num) +{ + g_assert(num < ARRAY_SIZE(soc->smbus)); + return I2C_BUS(qdev_get_child_bus(DEVICE(&soc->smbus[num]), "i2c-bus")); +} + +static void npcm750_evb_i2c_init(NPCM7xxState *soc) +{ + /* lm75 temperature sensor on SVB, tmp105 is compatible */ + i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 0), "tmp105", 0x48); + /* lm75 temperature sensor on EB, tmp105 is compatible */ + i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 1), "tmp105", 0x48); + /* tmp100 temperature sensor on EB, tmp105 is compatible */ + i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 2), "tmp105", 0x48); + /* tmp100 temperature sensor on SVB, tmp105 is compatible */ + i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 6), "tmp105", 0x48); +} + static void npcm750_evb_init(MachineState *machine) { NPCM7xxState *soc; @@ -XXX,XX +XXX,XX @@ static void npcm750_evb_init(MachineState *machine) npcm7xx_load_bootrom(machine, soc); npcm7xx_connect_flash(&soc->fiu[0], 0, "w25q256", drive_get(IF_MTD, 0, 0)); + npcm750_evb_i2c_init(soc); npcm7xx_load_kernel(machine, soc); } -- 2.20.1
From: Hao Wu <wuhaotsh@google.com> Add AT24 EEPROM and temperature sensors for GSJ machine. Reviewed-by: Doug Evans<dje@google.com> Reviewed-by: Tyrong Ting<kfting@nuvoton.com> Signed-off-by: Hao Wu <wuhaotsh@google.com> Message-id: 20210210220426.3577804-4-wuhaotsh@google.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/npcm7xx_boards.c | 27 +++++++++++++++++++++++++++ hw/arm/Kconfig | 1 + 2 files changed, 28 insertions(+) diff --git a/hw/arm/npcm7xx_boards.c b/hw/arm/npcm7xx_boards.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/npcm7xx_boards.c +++ b/hw/arm/npcm7xx_boards.c @@ -XXX,XX +XXX,XX @@ #include "exec/address-spaces.h" #include "hw/arm/npcm7xx.h" #include "hw/core/cpu.h" +#include "hw/i2c/smbus_eeprom.h" #include "hw/loader.h" #include "hw/qdev-properties.h" #include "qapi/error.h" @@ -XXX,XX +XXX,XX @@ static I2CBus *npcm7xx_i2c_get_bus(NPCM7xxState *soc, uint32_t num) return I2C_BUS(qdev_get_child_bus(DEVICE(&soc->smbus[num]), "i2c-bus")); } +static void at24c_eeprom_init(NPCM7xxState *soc, int bus, uint8_t addr, + uint32_t rsize) +{ + I2CBus *i2c_bus = npcm7xx_i2c_get_bus(soc, bus); + I2CSlave *i2c_dev = i2c_slave_new("at24c-eeprom", addr); + DeviceState *dev = DEVICE(i2c_dev); + + qdev_prop_set_uint32(dev, "rom-size", rsize); + i2c_slave_realize_and_unref(i2c_dev, i2c_bus, &error_abort); +} + static void npcm750_evb_i2c_init(NPCM7xxState *soc) { /* lm75 temperature sensor on SVB, tmp105 is compatible */ @@ -XXX,XX +XXX,XX @@ static void npcm750_evb_i2c_init(NPCM7xxState *soc) i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 6), "tmp105", 0x48); } +static void quanta_gsj_i2c_init(NPCM7xxState *soc) +{ + /* GSJ machine have 4 max31725 temperature sensors, tmp105 is compatible. */ + i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 1), "tmp105", 0x5c); + i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 2), "tmp105", 0x5c); + i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 3), "tmp105", 0x5c); + i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 4), "tmp105", 0x5c); + + at24c_eeprom_init(soc, 9, 0x55, 8192); + at24c_eeprom_init(soc, 10, 0x55, 8192); + + /* TODO: Add additional i2c devices. */ +} + static void npcm750_evb_init(MachineState *machine) { NPCM7xxState *soc; @@ -XXX,XX +XXX,XX @@ static void quanta_gsj_init(MachineState *machine) npcm7xx_load_bootrom(machine, soc); npcm7xx_connect_flash(&soc->fiu[0], 0, "mx25l25635e", drive_get(IF_MTD, 0, 0)); + quanta_gsj_i2c_init(soc); npcm7xx_load_kernel(machine, soc); } diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config NPCM7XX bool select A9MPCORE select ARM_GIC + select AT24C # EEPROM select PL310 # cache controller select SERIAL select SSI -- 2.20.1
From: Hao Wu <wuhaotsh@google.com> This patch adds a QTest for NPCM7XX SMBus's single byte mode. It sends a byte to a device in the evaluation board, and verify the retrieved value is equivalent to the sent value. Reviewed-by: Doug Evans<dje@google.com> Reviewed-by: Tyrong Ting<kfting@nuvoton.com> Signed-off-by: Hao Wu <wuhaotsh@google.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20210210220426.3577804-5-wuhaotsh@google.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- tests/qtest/npcm7xx_smbus-test.c | 352 +++++++++++++++++++++++++++++++ tests/qtest/meson.build | 1 + 2 files changed, 353 insertions(+) create mode 100644 tests/qtest/npcm7xx_smbus-test.c diff --git a/tests/qtest/npcm7xx_smbus-test.c b/tests/qtest/npcm7xx_smbus-test.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/npcm7xx_smbus-test.c @@ -XXX,XX +XXX,XX @@ +/* + * QTests for Nuvoton NPCM7xx SMBus Modules. + * + * 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/bitops.h" +#include "libqos/i2c.h" +#include "libqos/libqtest.h" +#include "hw/misc/tmp105_regs.h" + +#define NR_SMBUS_DEVICES 16 +#define SMBUS_ADDR(x) (0xf0080000 + 0x1000 * (x)) +#define SMBUS_IRQ(x) (64 + (x)) + +#define EVB_DEVICE_ADDR 0x48 +#define INVALID_DEVICE_ADDR 0x01 + +const int evb_bus_list[] = {0, 1, 2, 6}; + +/* Offsets */ +enum CommonRegister { + OFFSET_SDA = 0x0, + OFFSET_ST = 0x2, + OFFSET_CST = 0x4, + OFFSET_CTL1 = 0x6, + OFFSET_ADDR1 = 0x8, + OFFSET_CTL2 = 0xa, + OFFSET_ADDR2 = 0xc, + OFFSET_CTL3 = 0xe, + OFFSET_CST2 = 0x18, + OFFSET_CST3 = 0x19, +}; + +enum NPCM7xxSMBusBank0Register { + OFFSET_ADDR3 = 0x10, + OFFSET_ADDR7 = 0x11, + OFFSET_ADDR4 = 0x12, + OFFSET_ADDR8 = 0x13, + OFFSET_ADDR5 = 0x14, + OFFSET_ADDR9 = 0x15, + OFFSET_ADDR6 = 0x16, + OFFSET_ADDR10 = 0x17, + OFFSET_CTL4 = 0x1a, + OFFSET_CTL5 = 0x1b, + OFFSET_SCLLT = 0x1c, + OFFSET_FIF_CTL = 0x1d, + OFFSET_SCLHT = 0x1e, +}; + +enum NPCM7xxSMBusBank1Register { + OFFSET_FIF_CTS = 0x10, + OFFSET_FAIR_PER = 0x11, + OFFSET_TXF_CTL = 0x12, + OFFSET_T_OUT = 0x14, + OFFSET_TXF_STS = 0x1a, + OFFSET_RXF_STS = 0x1c, + OFFSET_RXF_CTL = 0x1e, +}; + +/* ST fields */ +#define ST_STP BIT(7) +#define ST_SDAST BIT(6) +#define ST_BER BIT(5) +#define ST_NEGACK BIT(4) +#define ST_STASTR BIT(3) +#define ST_NMATCH BIT(2) +#define ST_MODE BIT(1) +#define ST_XMIT BIT(0) + +/* CST fields */ +#define CST_ARPMATCH BIT(7) +#define CST_MATCHAF BIT(6) +#define CST_TGSCL BIT(5) +#define CST_TSDA BIT(4) +#define CST_GCMATCH BIT(3) +#define CST_MATCH BIT(2) +#define CST_BB BIT(1) +#define CST_BUSY BIT(0) + +/* CST2 fields */ +#define CST2_INSTTS BIT(7) +#define CST2_MATCH7F BIT(6) +#define CST2_MATCH6F BIT(5) +#define CST2_MATCH5F BIT(4) +#define CST2_MATCH4F BIT(3) +#define CST2_MATCH3F BIT(2) +#define CST2_MATCH2F BIT(1) +#define CST2_MATCH1F BIT(0) + +/* CST3 fields */ +#define CST3_EO_BUSY BIT(7) +#define CST3_MATCH10F BIT(2) +#define CST3_MATCH9F BIT(1) +#define CST3_MATCH8F BIT(0) + +/* CTL1 fields */ +#define CTL1_STASTRE BIT(7) +#define CTL1_NMINTE BIT(6) +#define CTL1_GCMEN BIT(5) +#define CTL1_ACK BIT(4) +#define CTL1_EOBINTE BIT(3) +#define CTL1_INTEN BIT(2) +#define CTL1_STOP BIT(1) +#define CTL1_START BIT(0) + +/* CTL2 fields */ +#define CTL2_SCLFRQ(rv) extract8((rv), 1, 6) +#define CTL2_ENABLE BIT(0) + +/* CTL3 fields */ +#define CTL3_SCL_LVL BIT(7) +#define CTL3_SDA_LVL BIT(6) +#define CTL3_BNK_SEL BIT(5) +#define CTL3_400K_MODE BIT(4) +#define CTL3_IDL_START BIT(3) +#define CTL3_ARPMEN BIT(2) +#define CTL3_SCLFRQ(rv) extract8((rv), 0, 2) + +/* ADDR fields */ +#define ADDR_EN BIT(7) +#define ADDR_A(rv) extract8((rv), 0, 6) + + +static void check_running(QTestState *qts, uint64_t base_addr) +{ + g_assert_true(qtest_readb(qts, base_addr + OFFSET_CST) & CST_BUSY); + g_assert_true(qtest_readb(qts, base_addr + OFFSET_CST) & CST_BB); +} + +static void check_stopped(QTestState *qts, uint64_t base_addr) +{ + uint8_t cst3; + + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, 0); + g_assert_false(qtest_readb(qts, base_addr + OFFSET_CST) & CST_BUSY); + g_assert_false(qtest_readb(qts, base_addr + OFFSET_CST) & CST_BB); + + cst3 = qtest_readb(qts, base_addr + OFFSET_CST3); + g_assert_true(cst3 & CST3_EO_BUSY); + qtest_writeb(qts, base_addr + OFFSET_CST3, cst3); + cst3 = qtest_readb(qts, base_addr + OFFSET_CST3); + g_assert_false(cst3 & CST3_EO_BUSY); +} + +static void enable_bus(QTestState *qts, uint64_t base_addr) +{ + uint8_t ctl2 = qtest_readb(qts, base_addr + OFFSET_CTL2); + + ctl2 |= CTL2_ENABLE; + qtest_writeb(qts, base_addr + OFFSET_CTL2, ctl2); + g_assert_true(qtest_readb(qts, base_addr + OFFSET_CTL2) & CTL2_ENABLE); +} + +static void disable_bus(QTestState *qts, uint64_t base_addr) +{ + uint8_t ctl2 = qtest_readb(qts, base_addr + OFFSET_CTL2); + + ctl2 &= ~CTL2_ENABLE; + qtest_writeb(qts, base_addr + OFFSET_CTL2, ctl2); + g_assert_false(qtest_readb(qts, base_addr + OFFSET_CTL2) & CTL2_ENABLE); +} + +static void start_transfer(QTestState *qts, uint64_t base_addr) +{ + uint8_t ctl1; + + ctl1 = CTL1_START | CTL1_INTEN | CTL1_STASTRE; + qtest_writeb(qts, base_addr + OFFSET_CTL1, ctl1); + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_CTL1), ==, + CTL1_INTEN | CTL1_STASTRE | CTL1_INTEN); + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, + ST_MODE | ST_XMIT | ST_SDAST); + check_running(qts, base_addr); +} + +static void stop_transfer(QTestState *qts, uint64_t base_addr) +{ + uint8_t ctl1 = qtest_readb(qts, base_addr + OFFSET_CTL1); + + ctl1 &= ~(CTL1_START | CTL1_ACK); + ctl1 |= CTL1_STOP | CTL1_INTEN | CTL1_EOBINTE; + qtest_writeb(qts, base_addr + OFFSET_CTL1, ctl1); + ctl1 = qtest_readb(qts, base_addr + OFFSET_CTL1); + g_assert_false(ctl1 & CTL1_STOP); +} + +static void send_byte(QTestState *qts, uint64_t base_addr, uint8_t byte) +{ + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, + ST_MODE | ST_XMIT | ST_SDAST); + qtest_writeb(qts, base_addr + OFFSET_SDA, byte); +} + +static uint8_t recv_byte(QTestState *qts, uint64_t base_addr) +{ + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, + ST_MODE | ST_SDAST); + return qtest_readb(qts, base_addr + OFFSET_SDA); +} + +static void send_address(QTestState *qts, uint64_t base_addr, uint8_t addr, + bool recv, bool valid) +{ + uint8_t encoded_addr = (addr << 1) | (recv ? 1 : 0); + uint8_t st; + + qtest_writeb(qts, base_addr + OFFSET_SDA, encoded_addr); + st = qtest_readb(qts, base_addr + OFFSET_ST); + + if (valid) { + if (recv) { + g_assert_cmphex(st, ==, ST_MODE | ST_SDAST | ST_STASTR); + } else { + g_assert_cmphex(st, ==, ST_MODE | ST_XMIT | ST_SDAST | ST_STASTR); + } + + qtest_writeb(qts, base_addr + OFFSET_ST, ST_STASTR); + st = qtest_readb(qts, base_addr + OFFSET_ST); + if (recv) { + g_assert_cmphex(st, ==, ST_MODE | ST_SDAST); + } else { + g_assert_cmphex(st, ==, ST_MODE | ST_XMIT | ST_SDAST); + } + } else { + if (recv) { + g_assert_cmphex(st, ==, ST_MODE | ST_NEGACK); + } else { + g_assert_cmphex(st, ==, ST_MODE | ST_XMIT | ST_NEGACK); + } + } +} + +static void send_nack(QTestState *qts, uint64_t base_addr) +{ + uint8_t ctl1 = qtest_readb(qts, base_addr + OFFSET_CTL1); + + ctl1 &= ~(CTL1_START | CTL1_STOP); + ctl1 |= CTL1_ACK | CTL1_INTEN; + qtest_writeb(qts, base_addr + OFFSET_CTL1, ctl1); +} + +/* Check the SMBus's status is set correctly when disabled. */ +static void test_disable_bus(gconstpointer data) +{ + intptr_t index = (intptr_t)data; + uint64_t base_addr = SMBUS_ADDR(index); + QTestState *qts = qtest_init("-machine npcm750-evb"); + + disable_bus(qts, base_addr); + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_CTL1), ==, 0); + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, 0); + g_assert_false(qtest_readb(qts, base_addr + OFFSET_CST3) & CST3_EO_BUSY); + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_CST), ==, 0); + qtest_quit(qts); +} + +/* Check the SMBus returns a NACK for an invalid address. */ +static void test_invalid_addr(gconstpointer data) +{ + intptr_t index = (intptr_t)data; + uint64_t base_addr = SMBUS_ADDR(index); + int irq = SMBUS_IRQ(index); + QTestState *qts = qtest_init("-machine npcm750-evb"); + + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + enable_bus(qts, base_addr); + g_assert_false(qtest_get_irq(qts, irq)); + start_transfer(qts, base_addr); + send_address(qts, base_addr, INVALID_DEVICE_ADDR, false, false); + g_assert_true(qtest_get_irq(qts, irq)); + stop_transfer(qts, base_addr); + check_running(qts, base_addr); + qtest_writeb(qts, base_addr + OFFSET_ST, ST_NEGACK); + g_assert_false(qtest_readb(qts, base_addr + OFFSET_ST) & ST_NEGACK); + check_stopped(qts, base_addr); + qtest_quit(qts); +} + +/* Check the SMBus can send and receive bytes to a device in single mode. */ +static void test_single_mode(gconstpointer data) +{ + intptr_t index = (intptr_t)data; + uint64_t base_addr = SMBUS_ADDR(index); + int irq = SMBUS_IRQ(index); + uint8_t value = 0x60; + QTestState *qts = qtest_init("-machine npcm750-evb"); + + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + enable_bus(qts, base_addr); + + /* Sending */ + g_assert_false(qtest_get_irq(qts, irq)); + start_transfer(qts, base_addr); + g_assert_true(qtest_get_irq(qts, irq)); + send_address(qts, base_addr, EVB_DEVICE_ADDR, false, true); + send_byte(qts, base_addr, TMP105_REG_CONFIG); + send_byte(qts, base_addr, value); + stop_transfer(qts, base_addr); + check_stopped(qts, base_addr); + + /* Receiving */ + start_transfer(qts, base_addr); + send_address(qts, base_addr, EVB_DEVICE_ADDR, false, true); + send_byte(qts, base_addr, TMP105_REG_CONFIG); + start_transfer(qts, base_addr); + send_address(qts, base_addr, EVB_DEVICE_ADDR, true, true); + send_nack(qts, base_addr); + stop_transfer(qts, base_addr); + check_running(qts, base_addr); + g_assert_cmphex(recv_byte(qts, base_addr), ==, value); + check_stopped(qts, base_addr); + qtest_quit(qts); +} + +static void smbus_add_test(const char *name, int index, GTestDataFunc fn) +{ + g_autofree char *full_name = g_strdup_printf( + "npcm7xx_smbus[%d]/%s", index, name); + qtest_add_data_func(full_name, (void *)(intptr_t)index, fn); +} +#define add_test(name, td) smbus_add_test(#name, td, test_##name) + +int main(int argc, char **argv) +{ + int i; + + g_test_init(&argc, &argv, NULL); + g_test_set_nonfatal_assertions(); + + for (i = 0; i < NR_SMBUS_DEVICES; ++i) { + add_test(disable_bus, i); + add_test(invalid_addr, i); + } + + for (i = 0; i < ARRAY_SIZE(evb_bus_list); ++i) { + add_test(single_mode, evb_bus_list[i]); + } + + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -XXX,XX +XXX,XX @@ qtests_npcm7xx = \ 'npcm7xx_gpio-test', 'npcm7xx_pwm-test', 'npcm7xx_rng-test', + 'npcm7xx_smbus-test', 'npcm7xx_timer-test', 'npcm7xx_watchdog_timer-test'] qtests_arm = \ -- 2.20.1
From: Hao Wu <wuhaotsh@google.com> This patch implements the FIFO mode of the SMBus module. In FIFO, the user transmits or receives at most 16 bytes at a time. The FIFO mode allows the module to transmit large amount of data faster than single byte mode. Since we only added the device in a patch that is only a few commits away in the same patch set. We do not increase the VMstate version number in this special case. Reviewed-by: Doug Evans<dje@google.com> Reviewed-by: Tyrong Ting<kfting@nuvoton.com> Signed-off-by: Hao Wu <wuhaotsh@google.com> Reviewed-by: Corey Minyard <cminyard@mvista.com> Message-id: 20210210220426.3577804-6-wuhaotsh@google.com Acked-by: Corey Minyard <cminyard@mvista.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/hw/i2c/npcm7xx_smbus.h | 25 +++ hw/i2c/npcm7xx_smbus.c | 342 +++++++++++++++++++++++++++++-- tests/qtest/npcm7xx_smbus-test.c | 149 +++++++++++++- hw/i2c/trace-events | 1 + 4 files changed, 501 insertions(+), 16 deletions(-) diff --git a/include/hw/i2c/npcm7xx_smbus.h b/include/hw/i2c/npcm7xx_smbus.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/i2c/npcm7xx_smbus.h +++ b/include/hw/i2c/npcm7xx_smbus.h @@ -XXX,XX +XXX,XX @@ */ #define NPCM7XX_SMBUS_NR_ADDRS 10 +/* Size of the FIFO buffer. */ +#define NPCM7XX_SMBUS_FIFO_SIZE 16 + typedef enum NPCM7xxSMBusStatus { NPCM7XX_SMBUS_STATUS_IDLE, NPCM7XX_SMBUS_STATUS_SENDING, @@ -XXX,XX +XXX,XX @@ typedef enum NPCM7xxSMBusStatus { * @addr: The SMBus module's own addresses on the I2C bus. * @scllt: The SCL low time register. * @sclht: The SCL high time register. + * @fif_ctl: The FIFO control register. + * @fif_cts: The FIFO control status register. + * @fair_per: The fair preriod register. + * @txf_ctl: The transmit FIFO control register. + * @t_out: The SMBus timeout register. + * @txf_sts: The transmit FIFO status register. + * @rxf_sts: The receive FIFO status register. + * @rxf_ctl: The receive FIFO control register. + * @rx_fifo: The FIFO buffer for receiving in FIFO mode. + * @rx_cur: The current position of rx_fifo. * @status: The current status of the SMBus. */ typedef struct NPCM7xxSMBusState { @@ -XXX,XX +XXX,XX @@ typedef struct NPCM7xxSMBusState { uint8_t scllt; uint8_t sclht; + uint8_t fif_ctl; + uint8_t fif_cts; + uint8_t fair_per; + uint8_t txf_ctl; + uint8_t t_out; + uint8_t txf_sts; + uint8_t rxf_sts; + uint8_t rxf_ctl; + + uint8_t rx_fifo[NPCM7XX_SMBUS_FIFO_SIZE]; + uint8_t rx_cur; + NPCM7xxSMBusStatus status; } NPCM7xxSMBusState; diff --git a/hw/i2c/npcm7xx_smbus.c b/hw/i2c/npcm7xx_smbus.c index XXXXXXX..XXXXXXX 100644 --- a/hw/i2c/npcm7xx_smbus.c +++ b/hw/i2c/npcm7xx_smbus.c @@ -XXX,XX +XXX,XX @@ enum NPCM7xxSMBusBank1Register { #define NPCM7XX_ADDR_EN BIT(7) #define NPCM7XX_ADDR_A(rv) extract8((rv), 0, 6) +/* FIFO Mode Register Fields */ +/* FIF_CTL fields */ +#define NPCM7XX_SMBFIF_CTL_FIFO_EN BIT(4) +#define NPCM7XX_SMBFIF_CTL_FAIR_RDY_IE BIT(2) +#define NPCM7XX_SMBFIF_CTL_FAIR_RDY BIT(1) +#define NPCM7XX_SMBFIF_CTL_FAIR_BUSY BIT(0) +/* FIF_CTS fields */ +#define NPCM7XX_SMBFIF_CTS_STR BIT(7) +#define NPCM7XX_SMBFIF_CTS_CLR_FIFO BIT(6) +#define NPCM7XX_SMBFIF_CTS_RFTE_IE BIT(3) +#define NPCM7XX_SMBFIF_CTS_RXF_TXE BIT(1) +/* TXF_CTL fields */ +#define NPCM7XX_SMBTXF_CTL_THR_TXIE BIT(6) +#define NPCM7XX_SMBTXF_CTL_TX_THR(rv) extract8((rv), 0, 5) +/* T_OUT fields */ +#define NPCM7XX_SMBT_OUT_ST BIT(7) +#define NPCM7XX_SMBT_OUT_IE BIT(6) +#define NPCM7XX_SMBT_OUT_CLKDIV(rv) extract8((rv), 0, 6) +/* TXF_STS fields */ +#define NPCM7XX_SMBTXF_STS_TX_THST BIT(6) +#define NPCM7XX_SMBTXF_STS_TX_BYTES(rv) extract8((rv), 0, 5) +/* RXF_STS fields */ +#define NPCM7XX_SMBRXF_STS_RX_THST BIT(6) +#define NPCM7XX_SMBRXF_STS_RX_BYTES(rv) extract8((rv), 0, 5) +/* RXF_CTL fields */ +#define NPCM7XX_SMBRXF_CTL_THR_RXIE BIT(6) +#define NPCM7XX_SMBRXF_CTL_LAST BIT(5) +#define NPCM7XX_SMBRXF_CTL_RX_THR(rv) extract8((rv), 0, 5) + #define KEEP_OLD_BIT(o, n, b) (((n) & (~(b))) | ((o) & (b))) #define WRITE_ONE_CLEAR(o, n, b) ((n) & (b) ? (o) & (~(b)) : (o)) #define NPCM7XX_SMBUS_ENABLED(s) ((s)->ctl2 & NPCM7XX_SMBCTL2_ENABLE) +#define NPCM7XX_SMBUS_FIFO_ENABLED(s) ((s)->fif_ctl & \ + NPCM7XX_SMBFIF_CTL_FIFO_EN) /* VERSION fields values, read-only. */ #define NPCM7XX_SMBUS_VERSION_NUMBER 1 -#define NPCM7XX_SMBUS_VERSION_FIFO_SUPPORTED 0 +#define NPCM7XX_SMBUS_VERSION_FIFO_SUPPORTED 1 /* Reset values */ #define NPCM7XX_SMB_ST_INIT_VAL 0x00 @@ -XXX,XX +XXX,XX @@ enum NPCM7xxSMBusBank1Register { #define NPCM7XX_SMB_ADDR_INIT_VAL 0x00 #define NPCM7XX_SMB_SCLLT_INIT_VAL 0x00 #define NPCM7XX_SMB_SCLHT_INIT_VAL 0x00 +#define NPCM7XX_SMB_FIF_CTL_INIT_VAL 0x00 +#define NPCM7XX_SMB_FIF_CTS_INIT_VAL 0x00 +#define NPCM7XX_SMB_FAIR_PER_INIT_VAL 0x00 +#define NPCM7XX_SMB_TXF_CTL_INIT_VAL 0x00 +#define NPCM7XX_SMB_T_OUT_INIT_VAL 0x3f +#define NPCM7XX_SMB_TXF_STS_INIT_VAL 0x00 +#define NPCM7XX_SMB_RXF_STS_INIT_VAL 0x00 +#define NPCM7XX_SMB_RXF_CTL_INIT_VAL 0x01 static uint8_t npcm7xx_smbus_get_version(void) { @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_update_irq(NPCM7xxSMBusState *s) (s->ctl1 & NPCM7XX_SMBCTL1_STASTRE && s->st & NPCM7XX_SMBST_SDAST) || (s->ctl1 & NPCM7XX_SMBCTL1_EOBINTE && - s->cst3 & NPCM7XX_SMBCST3_EO_BUSY)); + s->cst3 & NPCM7XX_SMBCST3_EO_BUSY) || + (s->rxf_ctl & NPCM7XX_SMBRXF_CTL_THR_RXIE && + s->rxf_sts & NPCM7XX_SMBRXF_STS_RX_THST) || + (s->txf_ctl & NPCM7XX_SMBTXF_CTL_THR_TXIE && + s->txf_sts & NPCM7XX_SMBTXF_STS_TX_THST) || + (s->fif_cts & NPCM7XX_SMBFIF_CTS_RFTE_IE && + s->fif_cts & NPCM7XX_SMBFIF_CTS_RXF_TXE)); if (level) { s->cst2 |= NPCM7XX_SMBCST2_INTSTS; @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_nack(NPCM7xxSMBusState *s) s->status = NPCM7XX_SMBUS_STATUS_NEGACK; } +static void npcm7xx_smbus_clear_buffer(NPCM7xxSMBusState *s) +{ + s->fif_cts &= ~NPCM7XX_SMBFIF_CTS_RXF_TXE; + s->txf_sts = 0; + s->rxf_sts = 0; +} + static void npcm7xx_smbus_send_byte(NPCM7xxSMBusState *s, uint8_t value) { int rv = i2c_send(s->bus, value); @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_send_byte(NPCM7xxSMBusState *s, uint8_t value) npcm7xx_smbus_nack(s); } else { s->st |= NPCM7XX_SMBST_SDAST; + if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) { + s->fif_cts |= NPCM7XX_SMBFIF_CTS_RXF_TXE; + if (NPCM7XX_SMBTXF_STS_TX_BYTES(s->txf_sts) == + NPCM7XX_SMBTXF_CTL_TX_THR(s->txf_ctl)) { + s->txf_sts = NPCM7XX_SMBTXF_STS_TX_THST; + } else { + s->txf_sts = 0; + } + } } trace_npcm7xx_smbus_send_byte((DEVICE(s)->canonical_path), value, !rv); npcm7xx_smbus_update_irq(s); @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_recv_byte(NPCM7xxSMBusState *s) npcm7xx_smbus_update_irq(s); } +static void npcm7xx_smbus_recv_fifo(NPCM7xxSMBusState *s) +{ + uint8_t expected_bytes = NPCM7XX_SMBRXF_CTL_RX_THR(s->rxf_ctl); + uint8_t received_bytes = NPCM7XX_SMBRXF_STS_RX_BYTES(s->rxf_sts); + uint8_t pos; + + if (received_bytes == expected_bytes) { + return; + } + + while (received_bytes < expected_bytes && + received_bytes < NPCM7XX_SMBUS_FIFO_SIZE) { + pos = (s->rx_cur + received_bytes) % NPCM7XX_SMBUS_FIFO_SIZE; + s->rx_fifo[pos] = i2c_recv(s->bus); + trace_npcm7xx_smbus_recv_byte((DEVICE(s)->canonical_path), + s->rx_fifo[pos]); + ++received_bytes; + } + + trace_npcm7xx_smbus_recv_fifo((DEVICE(s)->canonical_path), + received_bytes, expected_bytes); + s->rxf_sts = received_bytes; + if (unlikely(received_bytes < expected_bytes)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid rx_thr value: 0x%02x\n", + DEVICE(s)->canonical_path, expected_bytes); + return; + } + + s->rxf_sts |= NPCM7XX_SMBRXF_STS_RX_THST; + if (s->rxf_ctl & NPCM7XX_SMBRXF_CTL_LAST) { + trace_npcm7xx_smbus_nack(DEVICE(s)->canonical_path); + i2c_nack(s->bus); + s->rxf_ctl &= ~NPCM7XX_SMBRXF_CTL_LAST; + } + if (received_bytes == NPCM7XX_SMBUS_FIFO_SIZE) { + s->st |= NPCM7XX_SMBST_SDAST; + s->fif_cts |= NPCM7XX_SMBFIF_CTS_RXF_TXE; + } else if (!(s->rxf_ctl & NPCM7XX_SMBRXF_CTL_THR_RXIE)) { + s->st |= NPCM7XX_SMBST_SDAST; + } else { + s->st &= ~NPCM7XX_SMBST_SDAST; + } + npcm7xx_smbus_update_irq(s); +} + +static void npcm7xx_smbus_read_byte_fifo(NPCM7xxSMBusState *s) +{ + uint8_t received_bytes = NPCM7XX_SMBRXF_STS_RX_BYTES(s->rxf_sts); + + if (received_bytes == 0) { + npcm7xx_smbus_recv_fifo(s); + return; + } + + s->sda = s->rx_fifo[s->rx_cur]; + s->rx_cur = (s->rx_cur + 1u) % NPCM7XX_SMBUS_FIFO_SIZE; + --s->rxf_sts; + npcm7xx_smbus_update_irq(s); +} + static void npcm7xx_smbus_start(NPCM7xxSMBusState *s) { /* @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_start(NPCM7xxSMBusState *s) if (available) { s->st |= NPCM7XX_SMBST_MODE | NPCM7XX_SMBST_XMIT | NPCM7XX_SMBST_SDAST; s->cst |= NPCM7XX_SMBCST_BUSY; + if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) { + s->fif_cts |= NPCM7XX_SMBFIF_CTS_RXF_TXE; + } } else { s->st &= ~NPCM7XX_SMBST_MODE; s->cst &= ~NPCM7XX_SMBCST_BUSY; @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_send_address(NPCM7xxSMBusState *s, uint8_t value) s->st |= NPCM7XX_SMBST_SDAST; } } else if (recv) { - npcm7xx_smbus_recv_byte(s); + s->st |= NPCM7XX_SMBST_SDAST; + if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) { + npcm7xx_smbus_recv_fifo(s); + } else { + npcm7xx_smbus_recv_byte(s); + } + } else if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) { + s->st |= NPCM7XX_SMBST_SDAST; + s->fif_cts |= NPCM7XX_SMBFIF_CTS_RXF_TXE; } npcm7xx_smbus_update_irq(s); } @@ -XXX,XX +XXX,XX @@ static uint8_t npcm7xx_smbus_read_sda(NPCM7xxSMBusState *s) switch (s->status) { case NPCM7XX_SMBUS_STATUS_STOPPING_LAST_RECEIVE: - npcm7xx_smbus_execute_stop(s); + if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) { + if (NPCM7XX_SMBRXF_STS_RX_BYTES(s->rxf_sts) <= 1) { + npcm7xx_smbus_execute_stop(s); + } + if (NPCM7XX_SMBRXF_STS_RX_BYTES(s->rxf_sts) == 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read to SDA with an empty rx-fifo buffer, " + "result undefined: %u\n", + DEVICE(s)->canonical_path, s->sda); + break; + } + npcm7xx_smbus_read_byte_fifo(s); + value = s->sda; + } else { + npcm7xx_smbus_execute_stop(s); + } break; case NPCM7XX_SMBUS_STATUS_RECEIVING: - npcm7xx_smbus_recv_byte(s); + if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) { + npcm7xx_smbus_read_byte_fifo(s); + value = s->sda; + } else { + npcm7xx_smbus_recv_byte(s); + } break; default: @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_write_st(NPCM7xxSMBusState *s, uint8_t value) } if (value & NPCM7XX_SMBST_STASTR && - s->status == NPCM7XX_SMBUS_STATUS_RECEIVING) { - npcm7xx_smbus_recv_byte(s); + s->status == NPCM7XX_SMBUS_STATUS_RECEIVING) { + if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) { + npcm7xx_smbus_recv_fifo(s); + } else { + npcm7xx_smbus_recv_byte(s); + } } npcm7xx_smbus_update_irq(s); @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_write_ctl2(NPCM7xxSMBusState *s, uint8_t value) s->st = 0; s->cst3 = s->cst3 & (~NPCM7XX_SMBCST3_EO_BUSY); s->cst = 0; + npcm7xx_smbus_clear_buffer(s); } } @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_write_ctl3(NPCM7xxSMBusState *s, uint8_t value) NPCM7XX_SMBCTL3_SCL_LVL | NPCM7XX_SMBCTL3_SDA_LVL); } +static void npcm7xx_smbus_write_fif_ctl(NPCM7xxSMBusState *s, uint8_t value) +{ + uint8_t new_ctl = value; + + new_ctl = KEEP_OLD_BIT(s->fif_ctl, new_ctl, NPCM7XX_SMBFIF_CTL_FAIR_RDY); + new_ctl = WRITE_ONE_CLEAR(new_ctl, value, NPCM7XX_SMBFIF_CTL_FAIR_RDY); + new_ctl = KEEP_OLD_BIT(s->fif_ctl, new_ctl, NPCM7XX_SMBFIF_CTL_FAIR_BUSY); + s->fif_ctl = new_ctl; +} + +static void npcm7xx_smbus_write_fif_cts(NPCM7xxSMBusState *s, uint8_t value) +{ + s->fif_cts = WRITE_ONE_CLEAR(s->fif_cts, value, NPCM7XX_SMBFIF_CTS_STR); + s->fif_cts = WRITE_ONE_CLEAR(s->fif_cts, value, NPCM7XX_SMBFIF_CTS_RXF_TXE); + s->fif_cts = KEEP_OLD_BIT(value, s->fif_cts, NPCM7XX_SMBFIF_CTS_RFTE_IE); + + if (value & NPCM7XX_SMBFIF_CTS_CLR_FIFO) { + npcm7xx_smbus_clear_buffer(s); + } +} + +static void npcm7xx_smbus_write_txf_ctl(NPCM7xxSMBusState *s, uint8_t value) +{ + s->txf_ctl = value; +} + +static void npcm7xx_smbus_write_t_out(NPCM7xxSMBusState *s, uint8_t value) +{ + uint8_t new_t_out = value; + + if ((value & NPCM7XX_SMBT_OUT_ST) || (!(s->t_out & NPCM7XX_SMBT_OUT_ST))) { + new_t_out &= ~NPCM7XX_SMBT_OUT_ST; + } else { + new_t_out |= NPCM7XX_SMBT_OUT_ST; + } + + s->t_out = new_t_out; +} + +static void npcm7xx_smbus_write_txf_sts(NPCM7xxSMBusState *s, uint8_t value) +{ + s->txf_sts = WRITE_ONE_CLEAR(s->txf_sts, value, NPCM7XX_SMBTXF_STS_TX_THST); +} + +static void npcm7xx_smbus_write_rxf_sts(NPCM7xxSMBusState *s, uint8_t value) +{ + if (value & NPCM7XX_SMBRXF_STS_RX_THST) { + s->rxf_sts &= ~NPCM7XX_SMBRXF_STS_RX_THST; + if (s->status == NPCM7XX_SMBUS_STATUS_RECEIVING) { + npcm7xx_smbus_recv_fifo(s); + } + } +} + +static void npcm7xx_smbus_write_rxf_ctl(NPCM7xxSMBusState *s, uint8_t value) +{ + uint8_t new_ctl = value; + + if (!(value & NPCM7XX_SMBRXF_CTL_LAST)) { + new_ctl = KEEP_OLD_BIT(s->rxf_ctl, new_ctl, NPCM7XX_SMBRXF_CTL_LAST); + } + s->rxf_ctl = new_ctl; +} + static uint64_t npcm7xx_smbus_read(void *opaque, hwaddr offset, unsigned size) { NPCM7xxSMBusState *s = opaque; @@ -XXX,XX +XXX,XX @@ static uint64_t npcm7xx_smbus_read(void *opaque, hwaddr offset, unsigned size) default: if (bank) { /* Bank 1 */ - qemu_log_mask(LOG_GUEST_ERROR, - "%s: read from invalid offset 0x%" HWADDR_PRIx "\n", - DEVICE(s)->canonical_path, offset); + switch (offset) { + case NPCM7XX_SMB_FIF_CTS: + value = s->fif_cts; + break; + + case NPCM7XX_SMB_FAIR_PER: + value = s->fair_per; + break; + + case NPCM7XX_SMB_TXF_CTL: + value = s->txf_ctl; + break; + + case NPCM7XX_SMB_T_OUT: + value = s->t_out; + break; + + case NPCM7XX_SMB_TXF_STS: + value = s->txf_sts; + break; + + case NPCM7XX_SMB_RXF_STS: + value = s->rxf_sts; + break; + + case NPCM7XX_SMB_RXF_CTL: + value = s->rxf_ctl; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + } } else { /* Bank 0 */ switch (offset) { @@ -XXX,XX +XXX,XX @@ static uint64_t npcm7xx_smbus_read(void *opaque, hwaddr offset, unsigned size) value = s->scllt; break; + case NPCM7XX_SMB_FIF_CTL: + value = s->fif_ctl; + break; + case NPCM7XX_SMB_SCLHT: value = s->sclht; break; @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_write(void *opaque, hwaddr offset, uint64_t value, default: if (bank) { /* Bank 1 */ - qemu_log_mask(LOG_GUEST_ERROR, - "%s: write to invalid offset 0x%" HWADDR_PRIx "\n", - DEVICE(s)->canonical_path, offset); + switch (offset) { + case NPCM7XX_SMB_FIF_CTS: + npcm7xx_smbus_write_fif_cts(s, value); + break; + + case NPCM7XX_SMB_FAIR_PER: + s->fair_per = value; + break; + + case NPCM7XX_SMB_TXF_CTL: + npcm7xx_smbus_write_txf_ctl(s, value); + break; + + case NPCM7XX_SMB_T_OUT: + npcm7xx_smbus_write_t_out(s, value); + break; + + case NPCM7XX_SMB_TXF_STS: + npcm7xx_smbus_write_txf_sts(s, value); + break; + + case NPCM7XX_SMB_RXF_STS: + npcm7xx_smbus_write_rxf_sts(s, value); + break; + + case NPCM7XX_SMB_RXF_CTL: + npcm7xx_smbus_write_rxf_ctl(s, value); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + } } else { /* Bank 0 */ switch (offset) { @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_write(void *opaque, hwaddr offset, uint64_t value, s->scllt = value; break; + case NPCM7XX_SMB_FIF_CTL: + npcm7xx_smbus_write_fif_ctl(s, value); + break; + case NPCM7XX_SMB_SCLHT: s->sclht = value; break; @@ -XXX,XX +XXX,XX @@ static void npcm7xx_smbus_enter_reset(Object *obj, ResetType type) s->scllt = NPCM7XX_SMB_SCLLT_INIT_VAL; s->sclht = NPCM7XX_SMB_SCLHT_INIT_VAL; + s->fif_ctl = NPCM7XX_SMB_FIF_CTL_INIT_VAL; + s->fif_cts = NPCM7XX_SMB_FIF_CTS_INIT_VAL; + s->fair_per = NPCM7XX_SMB_FAIR_PER_INIT_VAL; + s->txf_ctl = NPCM7XX_SMB_TXF_CTL_INIT_VAL; + s->t_out = NPCM7XX_SMB_T_OUT_INIT_VAL; + s->txf_sts = NPCM7XX_SMB_TXF_STS_INIT_VAL; + s->rxf_sts = NPCM7XX_SMB_RXF_STS_INIT_VAL; + s->rxf_ctl = NPCM7XX_SMB_RXF_CTL_INIT_VAL; + + npcm7xx_smbus_clear_buffer(s); s->status = NPCM7XX_SMBUS_STATUS_IDLE; + s->rx_cur = 0; } static void npcm7xx_smbus_hold_reset(Object *obj) @@ -XXX,XX +XXX,XX @@ static const VMStateDescription vmstate_npcm7xx_smbus = { VMSTATE_UINT8_ARRAY(addr, NPCM7xxSMBusState, NPCM7XX_SMBUS_NR_ADDRS), VMSTATE_UINT8(scllt, NPCM7xxSMBusState), VMSTATE_UINT8(sclht, NPCM7xxSMBusState), + VMSTATE_UINT8(fif_ctl, NPCM7xxSMBusState), + VMSTATE_UINT8(fif_cts, NPCM7xxSMBusState), + VMSTATE_UINT8(fair_per, NPCM7xxSMBusState), + VMSTATE_UINT8(txf_ctl, NPCM7xxSMBusState), + VMSTATE_UINT8(t_out, NPCM7xxSMBusState), + VMSTATE_UINT8(txf_sts, NPCM7xxSMBusState), + VMSTATE_UINT8(rxf_sts, NPCM7xxSMBusState), + VMSTATE_UINT8(rxf_ctl, NPCM7xxSMBusState), + VMSTATE_UINT8_ARRAY(rx_fifo, NPCM7xxSMBusState, + NPCM7XX_SMBUS_FIFO_SIZE), + VMSTATE_UINT8(rx_cur, NPCM7xxSMBusState), VMSTATE_END_OF_LIST(), }, }; diff --git a/tests/qtest/npcm7xx_smbus-test.c b/tests/qtest/npcm7xx_smbus-test.c index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/npcm7xx_smbus-test.c +++ b/tests/qtest/npcm7xx_smbus-test.c @@ -XXX,XX +XXX,XX @@ enum NPCM7xxSMBusBank1Register { #define ADDR_EN BIT(7) #define ADDR_A(rv) extract8((rv), 0, 6) +/* FIF_CTL fields */ +#define FIF_CTL_FIFO_EN BIT(4) + +/* FIF_CTS fields */ +#define FIF_CTS_CLR_FIFO BIT(6) +#define FIF_CTS_RFTE_IE BIT(3) +#define FIF_CTS_RXF_TXE BIT(1) + +/* TXF_CTL fields */ +#define TXF_CTL_THR_TXIE BIT(6) +#define TXF_CTL_TX_THR(rv) extract8((rv), 0, 5) + +/* TXF_STS fields */ +#define TXF_STS_TX_THST BIT(6) +#define TXF_STS_TX_BYTES(rv) extract8((rv), 0, 5) + +/* RXF_CTL fields */ +#define RXF_CTL_THR_RXIE BIT(6) +#define RXF_CTL_LAST BIT(5) +#define RXF_CTL_RX_THR(rv) extract8((rv), 0, 5) + +/* RXF_STS fields */ +#define RXF_STS_RX_THST BIT(6) +#define RXF_STS_RX_BYTES(rv) extract8((rv), 0, 5) + + +static void choose_bank(QTestState *qts, uint64_t base_addr, uint8_t bank) +{ + uint8_t ctl3 = qtest_readb(qts, base_addr + OFFSET_CTL3); + + if (bank) { + ctl3 |= CTL3_BNK_SEL; + } else { + ctl3 &= ~CTL3_BNK_SEL; + } + + qtest_writeb(qts, base_addr + OFFSET_CTL3, ctl3); +} static void check_running(QTestState *qts, uint64_t base_addr) { @@ -XXX,XX +XXX,XX @@ static void send_byte(QTestState *qts, uint64_t base_addr, uint8_t byte) qtest_writeb(qts, base_addr + OFFSET_SDA, byte); } +static bool check_recv(QTestState *qts, uint64_t base_addr) +{ + uint8_t st, fif_ctl, rxf_ctl, rxf_sts; + bool fifo; + + st = qtest_readb(qts, base_addr + OFFSET_ST); + choose_bank(qts, base_addr, 0); + fif_ctl = qtest_readb(qts, base_addr + OFFSET_FIF_CTL); + fifo = fif_ctl & FIF_CTL_FIFO_EN; + if (!fifo) { + return st == (ST_MODE | ST_SDAST); + } + + choose_bank(qts, base_addr, 1); + rxf_ctl = qtest_readb(qts, base_addr + OFFSET_RXF_CTL); + rxf_sts = qtest_readb(qts, base_addr + OFFSET_RXF_STS); + + if ((rxf_ctl & RXF_CTL_THR_RXIE) && RXF_STS_RX_BYTES(rxf_sts) < 16) { + return st == ST_MODE; + } else { + return st == (ST_MODE | ST_SDAST); + } +} + static uint8_t recv_byte(QTestState *qts, uint64_t base_addr) { - g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, - ST_MODE | ST_SDAST); + g_assert_true(check_recv(qts, base_addr)); return qtest_readb(qts, base_addr + OFFSET_SDA); } @@ -XXX,XX +XXX,XX @@ static void send_address(QTestState *qts, uint64_t base_addr, uint8_t addr, qtest_writeb(qts, base_addr + OFFSET_ST, ST_STASTR); st = qtest_readb(qts, base_addr + OFFSET_ST); if (recv) { - g_assert_cmphex(st, ==, ST_MODE | ST_SDAST); + g_assert_true(check_recv(qts, base_addr)); } else { g_assert_cmphex(st, ==, ST_MODE | ST_XMIT | ST_SDAST); } @@ -XXX,XX +XXX,XX @@ static void send_nack(QTestState *qts, uint64_t base_addr) qtest_writeb(qts, base_addr + OFFSET_CTL1, ctl1); } +static void start_fifo_mode(QTestState *qts, uint64_t base_addr) +{ + choose_bank(qts, base_addr, 0); + qtest_writeb(qts, base_addr + OFFSET_FIF_CTL, FIF_CTL_FIFO_EN); + g_assert_true(qtest_readb(qts, base_addr + OFFSET_FIF_CTL) & + FIF_CTL_FIFO_EN); + choose_bank(qts, base_addr, 1); + qtest_writeb(qts, base_addr + OFFSET_FIF_CTS, + FIF_CTS_CLR_FIFO | FIF_CTS_RFTE_IE); + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_FIF_CTS), ==, + FIF_CTS_RFTE_IE); + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_TXF_STS), ==, 0); + g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_RXF_STS), ==, 0); +} + +static void start_recv_fifo(QTestState *qts, uint64_t base_addr, uint8_t bytes) +{ + choose_bank(qts, base_addr, 1); + qtest_writeb(qts, base_addr + OFFSET_TXF_CTL, 0); + qtest_writeb(qts, base_addr + OFFSET_RXF_CTL, + RXF_CTL_THR_RXIE | RXF_CTL_LAST | bytes); +} + /* Check the SMBus's status is set correctly when disabled. */ static void test_disable_bus(gconstpointer data) { @@ -XXX,XX +XXX,XX @@ static void test_single_mode(gconstpointer data) qtest_quit(qts); } +/* Check the SMBus can send and receive bytes in FIFO mode. */ +static void test_fifo_mode(gconstpointer data) +{ + intptr_t index = (intptr_t)data; + uint64_t base_addr = SMBUS_ADDR(index); + int irq = SMBUS_IRQ(index); + uint8_t value = 0x60; + QTestState *qts = qtest_init("-machine npcm750-evb"); + + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + enable_bus(qts, base_addr); + start_fifo_mode(qts, base_addr); + g_assert_false(qtest_get_irq(qts, irq)); + + /* Sending */ + start_transfer(qts, base_addr); + send_address(qts, base_addr, EVB_DEVICE_ADDR, false, true); + choose_bank(qts, base_addr, 1); + g_assert_true(qtest_readb(qts, base_addr + OFFSET_FIF_CTS) & + FIF_CTS_RXF_TXE); + qtest_writeb(qts, base_addr + OFFSET_TXF_CTL, TXF_CTL_THR_TXIE); + send_byte(qts, base_addr, TMP105_REG_CONFIG); + send_byte(qts, base_addr, value); + g_assert_true(qtest_readb(qts, base_addr + OFFSET_FIF_CTS) & + FIF_CTS_RXF_TXE); + g_assert_true(qtest_readb(qts, base_addr + OFFSET_TXF_STS) & + TXF_STS_TX_THST); + g_assert_cmpuint(TXF_STS_TX_BYTES( + qtest_readb(qts, base_addr + OFFSET_TXF_STS)), ==, 0); + g_assert_true(qtest_get_irq(qts, irq)); + stop_transfer(qts, base_addr); + check_stopped(qts, base_addr); + + /* Receiving */ + start_fifo_mode(qts, base_addr); + start_transfer(qts, base_addr); + send_address(qts, base_addr, EVB_DEVICE_ADDR, false, true); + send_byte(qts, base_addr, TMP105_REG_CONFIG); + start_transfer(qts, base_addr); + qtest_writeb(qts, base_addr + OFFSET_FIF_CTS, FIF_CTS_RXF_TXE); + start_recv_fifo(qts, base_addr, 1); + send_address(qts, base_addr, EVB_DEVICE_ADDR, true, true); + g_assert_false(qtest_readb(qts, base_addr + OFFSET_FIF_CTS) & + FIF_CTS_RXF_TXE); + g_assert_true(qtest_readb(qts, base_addr + OFFSET_RXF_STS) & + RXF_STS_RX_THST); + g_assert_cmpuint(RXF_STS_RX_BYTES( + qtest_readb(qts, base_addr + OFFSET_RXF_STS)), ==, 1); + send_nack(qts, base_addr); + stop_transfer(qts, base_addr); + check_running(qts, base_addr); + g_assert_cmphex(recv_byte(qts, base_addr), ==, value); + g_assert_cmpuint(RXF_STS_RX_BYTES( + qtest_readb(qts, base_addr + OFFSET_RXF_STS)), ==, 0); + check_stopped(qts, base_addr); + qtest_quit(qts); +} + static void smbus_add_test(const char *name, int index, GTestDataFunc fn) { g_autofree char *full_name = g_strdup_printf( @@ -XXX,XX +XXX,XX @@ int main(int argc, char **argv) for (i = 0; i < ARRAY_SIZE(evb_bus_list); ++i) { add_test(single_mode, evb_bus_list[i]); + add_test(fifo_mode, evb_bus_list[i]); } return g_test_run(); diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/i2c/trace-events +++ b/hw/i2c/trace-events @@ -XXX,XX +XXX,XX @@ npcm7xx_smbus_send_byte(const char *id, uint8_t value, int success) "%s send byt npcm7xx_smbus_recv_byte(const char *id, uint8_t value) "%s recv byte: 0x%02x" npcm7xx_smbus_stop(const char *id) "%s stopping" npcm7xx_smbus_nack(const char *id) "%s nacking" +npcm7xx_smbus_recv_fifo(const char *id, uint8_t received, uint8_t expected) "%s recv fifo: received %u, expected %u" -- 2.20.1
From: Luc Michel <luc@lmichel.fr> Also add Damien as a reviewer. Signed-off-by: Luc Michel <luc@lmichel.fr> Acked-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-id: 20210211085318.2507-1-luc@lmichel.fr Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- MAINTAINERS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: pc-bios/opensbi-* F: .gitlab-ci.d/opensbi.yml F: .gitlab-ci.d/opensbi/ +Clock framework +M: Luc Michel <luc@lmichel.fr> +R: Damien Hedde <damien.hedde@greensocs.com> +S: Maintained +F: include/hw/clock.h +F: include/hw/qdev-clock.h +F: hw/core/clock.c +F: hw/core/clock-vmstate.c +F: hw/core/qdev-clock.c +F: docs/devel/clocks.rst + Usermode Emulation ------------------ Overall usermode emulation -- 2.20.1
From: Doug Evans <dje@google.com> This is a 10/100 ethernet device that has several features. Only the ones needed by the Linux driver have been implemented. See npcm7xx_emc.c for a list of unimplemented features. Reviewed-by: Hao Wu <wuhaotsh@google.com> Reviewed-by: Avi Fishman <avi.fishman@nuvoton.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Doug Evans <dje@google.com> Message-id: 20210213002520.1374134-2-dje@google.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/hw/net/npcm7xx_emc.h | 286 ++++++++++++ hw/net/npcm7xx_emc.c | 857 +++++++++++++++++++++++++++++++++++ hw/net/meson.build | 1 + hw/net/trace-events | 17 + 4 files changed, 1161 insertions(+) create mode 100644 include/hw/net/npcm7xx_emc.h create mode 100644 hw/net/npcm7xx_emc.c diff --git a/include/hw/net/npcm7xx_emc.h b/include/hw/net/npcm7xx_emc.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/net/npcm7xx_emc.h @@ -XXX,XX +XXX,XX @@ +/* + * Nuvoton NPCM7xx EMC Module + * + * 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. + */ + +#ifndef NPCM7XX_EMC_H +#define NPCM7XX_EMC_H + +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "net/net.h" + +/* 32-bit register indices. */ +enum NPCM7xxPWMRegister { + /* Control registers. */ + REG_CAMCMR, + REG_CAMEN, + + /* There are 16 CAMn[ML] registers. */ + REG_CAMM_BASE, + REG_CAML_BASE, + REG_CAMML_LAST = 0x21, + + REG_TXDLSA = 0x22, + REG_RXDLSA, + REG_MCMDR, + REG_MIID, + REG_MIIDA, + REG_FFTCR, + REG_TSDR, + REG_RSDR, + REG_DMARFC, + REG_MIEN, + + /* Status registers. */ + REG_MISTA, + REG_MGSTA, + REG_MPCNT, + REG_MRPC, + REG_MRPCC, + REG_MREPC, + REG_DMARFS, + REG_CTXDSA, + REG_CTXBSA, + REG_CRXDSA, + REG_CRXBSA, + + NPCM7XX_NUM_EMC_REGS, +}; + +/* REG_CAMCMR fields */ +/* Enable CAM Compare */ +#define REG_CAMCMR_ECMP (1 << 4) +/* Complement CAM Compare */ +#define REG_CAMCMR_CCAM (1 << 3) +/* Accept Broadcast Packet */ +#define REG_CAMCMR_ABP (1 << 2) +/* Accept Multicast Packet */ +#define REG_CAMCMR_AMP (1 << 1) +/* Accept Unicast Packet */ +#define REG_CAMCMR_AUP (1 << 0) + +/* REG_MCMDR fields */ +/* Software Reset */ +#define REG_MCMDR_SWR (1 << 24) +/* Internal Loopback Select */ +#define REG_MCMDR_LBK (1 << 21) +/* Operation Mode Select */ +#define REG_MCMDR_OPMOD (1 << 20) +/* Enable MDC Clock Generation */ +#define REG_MCMDR_ENMDC (1 << 19) +/* Full-Duplex Mode Select */ +#define REG_MCMDR_FDUP (1 << 18) +/* Enable SQE Checking */ +#define REG_MCMDR_ENSEQ (1 << 17) +/* Send PAUSE Frame */ +#define REG_MCMDR_SDPZ (1 << 16) +/* No Defer */ +#define REG_MCMDR_NDEF (1 << 9) +/* Frame Transmission On */ +#define REG_MCMDR_TXON (1 << 8) +/* Strip CRC Checksum */ +#define REG_MCMDR_SPCRC (1 << 5) +/* Accept CRC Error Packet */ +#define REG_MCMDR_AEP (1 << 4) +/* Accept Control Packet */ +#define REG_MCMDR_ACP (1 << 3) +/* Accept Runt Packet */ +#define REG_MCMDR_ARP (1 << 2) +/* Accept Long Packet */ +#define REG_MCMDR_ALP (1 << 1) +/* Frame Reception On */ +#define REG_MCMDR_RXON (1 << 0) + +/* REG_MIEN fields */ +/* Enable Transmit Descriptor Unavailable Interrupt */ +#define REG_MIEN_ENTDU (1 << 23) +/* Enable Transmit Completion Interrupt */ +#define REG_MIEN_ENTXCP (1 << 18) +/* Enable Transmit Interrupt */ +#define REG_MIEN_ENTXINTR (1 << 16) +/* Enable Receive Descriptor Unavailable Interrupt */ +#define REG_MIEN_ENRDU (1 << 10) +/* Enable Receive Good Interrupt */ +#define REG_MIEN_ENRXGD (1 << 4) +/* Enable Receive Interrupt */ +#define REG_MIEN_ENRXINTR (1 << 0) + +/* REG_MISTA fields */ +/* TODO: Add error fields and support simulated errors? */ +/* Transmit Bus Error Interrupt */ +#define REG_MISTA_TXBERR (1 << 24) +/* Transmit Descriptor Unavailable Interrupt */ +#define REG_MISTA_TDU (1 << 23) +/* Transmit Completion Interrupt */ +#define REG_MISTA_TXCP (1 << 18) +/* Transmit Interrupt */ +#define REG_MISTA_TXINTR (1 << 16) +/* Receive Bus Error Interrupt */ +#define REG_MISTA_RXBERR (1 << 11) +/* Receive Descriptor Unavailable Interrupt */ +#define REG_MISTA_RDU (1 << 10) +/* DMA Early Notification Interrupt */ +#define REG_MISTA_DENI (1 << 9) +/* Maximum Frame Length Interrupt */ +#define REG_MISTA_DFOI (1 << 8) +/* Receive Good Interrupt */ +#define REG_MISTA_RXGD (1 << 4) +/* Packet Too Long Interrupt */ +#define REG_MISTA_PTLE (1 << 3) +/* Receive Interrupt */ +#define REG_MISTA_RXINTR (1 << 0) + +/* REG_MGSTA fields */ +/* Transmission Halted */ +#define REG_MGSTA_TXHA (1 << 11) +/* Receive Halted */ +#define REG_MGSTA_RXHA (1 << 11) + +/* REG_DMARFC fields */ +/* Maximum Receive Frame Length */ +#define REG_DMARFC_RXMS(word) extract32((word), 0, 16) + +/* REG MIIDA fields */ +/* Busy Bit */ +#define REG_MIIDA_BUSY (1 << 17) + +/* Transmit and receive descriptors */ +typedef struct NPCM7xxEMCTxDesc NPCM7xxEMCTxDesc; +typedef struct NPCM7xxEMCRxDesc NPCM7xxEMCRxDesc; + +struct NPCM7xxEMCTxDesc { + uint32_t flags; + uint32_t txbsa; + uint32_t status_and_length; + uint32_t ntxdsa; +}; + +struct NPCM7xxEMCRxDesc { + uint32_t status_and_length; + uint32_t rxbsa; + uint32_t reserved; + uint32_t nrxdsa; +}; + +/* NPCM7xxEMCTxDesc.flags values */ +/* Owner: 0 = cpu, 1 = emc */ +#define TX_DESC_FLAG_OWNER_MASK (1 << 31) +/* Transmit interrupt enable */ +#define TX_DESC_FLAG_INTEN (1 << 2) +/* CRC append */ +#define TX_DESC_FLAG_CRCAPP (1 << 1) +/* Padding enable */ +#define TX_DESC_FLAG_PADEN (1 << 0) + +/* NPCM7xxEMCTxDesc.status_and_length values */ +/* Collision count */ +#define TX_DESC_STATUS_CCNT_SHIFT 28 +#define TX_DESC_STATUS_CCNT_BITSIZE 4 +/* SQE error */ +#define TX_DESC_STATUS_SQE (1 << 26) +/* Transmission paused */ +#define TX_DESC_STATUS_PAU (1 << 25) +/* P transmission halted */ +#define TX_DESC_STATUS_TXHA (1 << 24) +/* Late collision */ +#define TX_DESC_STATUS_LC (1 << 23) +/* Transmission abort */ +#define TX_DESC_STATUS_TXABT (1 << 22) +/* No carrier sense */ +#define TX_DESC_STATUS_NCS (1 << 21) +/* Defer exceed */ +#define TX_DESC_STATUS_EXDEF (1 << 20) +/* Transmission complete */ +#define TX_DESC_STATUS_TXCP (1 << 19) +/* Transmission deferred */ +#define TX_DESC_STATUS_DEF (1 << 17) +/* Transmit interrupt */ +#define TX_DESC_STATUS_TXINTR (1 << 16) + +#define TX_DESC_PKT_LEN(word) extract32((word), 0, 16) + +/* Transmit buffer start address */ +#define TX_DESC_TXBSA(word) ((uint32_t) (word) & ~3u) + +/* Next transmit descriptor start address */ +#define TX_DESC_NTXDSA(word) ((uint32_t) (word) & ~3u) + +/* NPCM7xxEMCRxDesc.status_and_length values */ +/* Owner: 0b00 = cpu, 0b01 = undefined, 0b10 = emc, 0b11 = undefined */ +#define RX_DESC_STATUS_OWNER_SHIFT 30 +#define RX_DESC_STATUS_OWNER_BITSIZE 2 +#define RX_DESC_STATUS_OWNER_MASK (3 << RX_DESC_STATUS_OWNER_SHIFT) +/* Runt packet */ +#define RX_DESC_STATUS_RP (1 << 22) +/* Alignment error */ +#define RX_DESC_STATUS_ALIE (1 << 21) +/* Frame reception complete */ +#define RX_DESC_STATUS_RXGD (1 << 20) +/* Packet too long */ +#define RX_DESC_STATUS_PTLE (1 << 19) +/* CRC error */ +#define RX_DESC_STATUS_CRCE (1 << 17) +/* Receive interrupt */ +#define RX_DESC_STATUS_RXINTR (1 << 16) + +#define RX_DESC_PKT_LEN(word) extract32((word), 0, 16) + +/* Receive buffer start address */ +#define RX_DESC_RXBSA(word) ((uint32_t) (word) & ~3u) + +/* Next receive descriptor start address */ +#define RX_DESC_NRXDSA(word) ((uint32_t) (word) & ~3u) + +/* Minimum packet length, when TX_DESC_FLAG_PADEN is set. */ +#define MIN_PACKET_LENGTH 64 + +struct NPCM7xxEMCState { + /*< private >*/ + SysBusDevice parent; + /*< public >*/ + + MemoryRegion iomem; + + qemu_irq tx_irq; + qemu_irq rx_irq; + + NICState *nic; + NICConf conf; + + /* 0 or 1, for log messages */ + uint8_t emc_num; + + uint32_t regs[NPCM7XX_NUM_EMC_REGS]; + + /* + * tx is active. Set to true by TSDR and then switches off when out of + * descriptors. If the TXON bit in REG_MCMDR is off then this is off. + */ + bool tx_active; + + /* + * rx is active. Set to true by RSDR and then switches off when out of + * descriptors. If the RXON bit in REG_MCMDR is off then this is off. + */ + bool rx_active; +}; + +typedef struct NPCM7xxEMCState NPCM7xxEMCState; + +#define TYPE_NPCM7XX_EMC "npcm7xx-emc" +#define NPCM7XX_EMC(obj) \ + OBJECT_CHECK(NPCM7xxEMCState, (obj), TYPE_NPCM7XX_EMC) + +#endif /* NPCM7XX_EMC_H */ diff --git a/hw/net/npcm7xx_emc.c b/hw/net/npcm7xx_emc.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/net/npcm7xx_emc.c @@ -XXX,XX +XXX,XX @@ +/* + * Nuvoton NPCM7xx EMC Module + * + * 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. + * + * Unsupported/unimplemented features: + * - MCMDR.FDUP (full duplex) is ignored, half duplex is not supported + * - Only CAM0 is supported, CAM[1-15] are not + * - writes to CAMEN.[1-15] are ignored, these bits always read as zeroes + * - MII is not implemented, MIIDA.BUSY and MIID always return zero + * - MCMDR.LBK is not implemented + * - MCMDR.{OPMOD,ENSQE,AEP,ARP} are not supported + * - H/W FIFOs are not supported, MCMDR.FFTCR is ignored + * - MGSTA.SQE is not supported + * - pause and control frames are not implemented + * - MGSTA.CCNT is not supported + * - MPCNT, DMARFS are not implemented + */ + +#include "qemu/osdep.h" + +/* For crc32 */ +#include <zlib.h> + +#include "qemu-common.h" +#include "hw/irq.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-properties.h" +#include "hw/net/npcm7xx_emc.h" +#include "net/eth.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "sysemu/dma.h" +#include "trace.h" + +#define CRC_LENGTH 4 + +/* + * The maximum size of a (layer 2) ethernet frame as defined by 802.3. + * 1518 = 6(dest macaddr) + 6(src macaddr) + 2(proto) + 4(crc) + 1500(payload) + * This does not include an additional 4 for the vlan field (802.1q). + */ +#define MAX_ETH_FRAME_SIZE 1518 + +static const char *emc_reg_name(int regno) +{ +#define REG(name) case REG_ ## name: return #name; + switch (regno) { + REG(CAMCMR) + REG(CAMEN) + REG(TXDLSA) + REG(RXDLSA) + REG(MCMDR) + REG(MIID) + REG(MIIDA) + REG(FFTCR) + REG(TSDR) + REG(RSDR) + REG(DMARFC) + REG(MIEN) + REG(MISTA) + REG(MGSTA) + REG(MPCNT) + REG(MRPC) + REG(MRPCC) + REG(MREPC) + REG(DMARFS) + REG(CTXDSA) + REG(CTXBSA) + REG(CRXDSA) + REG(CRXBSA) + case REG_CAMM_BASE + 0: return "CAM0M"; + case REG_CAML_BASE + 0: return "CAM0L"; + case REG_CAMM_BASE + 2 ... REG_CAMML_LAST: + /* Only CAM0 is supported, fold the others into something simple. */ + if (regno & 1) { + return "CAM<n>L"; + } else { + return "CAM<n>M"; + } + default: return "UNKNOWN"; + } +#undef REG +} + +static void emc_reset(NPCM7xxEMCState *emc) +{ + trace_npcm7xx_emc_reset(emc->emc_num); + + memset(&emc->regs[0], 0, sizeof(emc->regs)); + + /* These regs have non-zero reset values. */ + emc->regs[REG_TXDLSA] = 0xfffffffc; + emc->regs[REG_RXDLSA] = 0xfffffffc; + emc->regs[REG_MIIDA] = 0x00900000; + emc->regs[REG_FFTCR] = 0x0101; + emc->regs[REG_DMARFC] = 0x0800; + emc->regs[REG_MPCNT] = 0x7fff; + + emc->tx_active = false; + emc->rx_active = false; +} + +static void npcm7xx_emc_reset(DeviceState *dev) +{ + NPCM7xxEMCState *emc = NPCM7XX_EMC(dev); + emc_reset(emc); +} + +static void emc_soft_reset(NPCM7xxEMCState *emc) +{ + /* + * The docs say at least MCMDR.{LBK,OPMOD} bits are not changed during a + * soft reset, but does not go into further detail. For now, KISS. + */ + uint32_t mcmdr = emc->regs[REG_MCMDR]; + emc_reset(emc); + emc->regs[REG_MCMDR] = mcmdr & (REG_MCMDR_LBK | REG_MCMDR_OPMOD); + + qemu_set_irq(emc->tx_irq, 0); + qemu_set_irq(emc->rx_irq, 0); +} + +static void emc_set_link(NetClientState *nc) +{ + /* Nothing to do yet. */ +} + +/* MISTA.TXINTR is the union of the individual bits with their enables. */ +static void emc_update_mista_txintr(NPCM7xxEMCState *emc) +{ + /* Only look at the bits we support. */ + uint32_t mask = (REG_MISTA_TXBERR | + REG_MISTA_TDU | + REG_MISTA_TXCP); + if (emc->regs[REG_MISTA] & emc->regs[REG_MIEN] & mask) { + emc->regs[REG_MISTA] |= REG_MISTA_TXINTR; + } else { + emc->regs[REG_MISTA] &= ~REG_MISTA_TXINTR; + } +} + +/* MISTA.RXINTR is the union of the individual bits with their enables. */ +static void emc_update_mista_rxintr(NPCM7xxEMCState *emc) +{ + /* Only look at the bits we support. */ + uint32_t mask = (REG_MISTA_RXBERR | + REG_MISTA_RDU | + REG_MISTA_RXGD); + if (emc->regs[REG_MISTA] & emc->regs[REG_MIEN] & mask) { + emc->regs[REG_MISTA] |= REG_MISTA_RXINTR; + } else { + emc->regs[REG_MISTA] &= ~REG_MISTA_RXINTR; + } +} + +/* N.B. emc_update_mista_txintr must have already been called. */ +static void emc_update_tx_irq(NPCM7xxEMCState *emc) +{ + int level = !!(emc->regs[REG_MISTA] & + emc->regs[REG_MIEN] & + REG_MISTA_TXINTR); + trace_npcm7xx_emc_update_tx_irq(level); + qemu_set_irq(emc->tx_irq, level); +} + +/* N.B. emc_update_mista_rxintr must have already been called. */ +static void emc_update_rx_irq(NPCM7xxEMCState *emc) +{ + int level = !!(emc->regs[REG_MISTA] & + emc->regs[REG_MIEN] & + REG_MISTA_RXINTR); + trace_npcm7xx_emc_update_rx_irq(level); + qemu_set_irq(emc->rx_irq, level); +} + +/* Update IRQ states due to changes in MIEN,MISTA. */ +static void emc_update_irq_from_reg_change(NPCM7xxEMCState *emc) +{ + emc_update_mista_txintr(emc); + emc_update_tx_irq(emc); + + emc_update_mista_rxintr(emc); + emc_update_rx_irq(emc); +} + +static int emc_read_tx_desc(dma_addr_t addr, NPCM7xxEMCTxDesc *desc) +{ + if (dma_memory_read(&address_space_memory, addr, desc, sizeof(*desc))) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to read descriptor @ 0x%" + HWADDR_PRIx "\n", __func__, addr); + return -1; + } + desc->flags = le32_to_cpu(desc->flags); + desc->txbsa = le32_to_cpu(desc->txbsa); + desc->status_and_length = le32_to_cpu(desc->status_and_length); + desc->ntxdsa = le32_to_cpu(desc->ntxdsa); + return 0; +} + +static int emc_write_tx_desc(const NPCM7xxEMCTxDesc *desc, dma_addr_t addr) +{ + NPCM7xxEMCTxDesc le_desc; + + le_desc.flags = cpu_to_le32(desc->flags); + le_desc.txbsa = cpu_to_le32(desc->txbsa); + le_desc.status_and_length = cpu_to_le32(desc->status_and_length); + le_desc.ntxdsa = cpu_to_le32(desc->ntxdsa); + if (dma_memory_write(&address_space_memory, addr, &le_desc, + sizeof(le_desc))) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to write descriptor @ 0x%" + HWADDR_PRIx "\n", __func__, addr); + return -1; + } + return 0; +} + +static int emc_read_rx_desc(dma_addr_t addr, NPCM7xxEMCRxDesc *desc) +{ + if (dma_memory_read(&address_space_memory, addr, desc, sizeof(*desc))) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to read descriptor @ 0x%" + HWADDR_PRIx "\n", __func__, addr); + return -1; + } + desc->status_and_length = le32_to_cpu(desc->status_and_length); + desc->rxbsa = le32_to_cpu(desc->rxbsa); + desc->reserved = le32_to_cpu(desc->reserved); + desc->nrxdsa = le32_to_cpu(desc->nrxdsa); + return 0; +} + +static int emc_write_rx_desc(const NPCM7xxEMCRxDesc *desc, dma_addr_t addr) +{ + NPCM7xxEMCRxDesc le_desc; + + le_desc.status_and_length = cpu_to_le32(desc->status_and_length); + le_desc.rxbsa = cpu_to_le32(desc->rxbsa); + le_desc.reserved = cpu_to_le32(desc->reserved); + le_desc.nrxdsa = cpu_to_le32(desc->nrxdsa); + if (dma_memory_write(&address_space_memory, addr, &le_desc, + sizeof(le_desc))) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to write descriptor @ 0x%" + HWADDR_PRIx "\n", __func__, addr); + return -1; + } + return 0; +} + +static void emc_set_mista(NPCM7xxEMCState *emc, uint32_t flags) +{ + trace_npcm7xx_emc_set_mista(flags); + emc->regs[REG_MISTA] |= flags; + if (extract32(flags, 16, 16)) { + emc_update_mista_txintr(emc); + } + if (extract32(flags, 0, 16)) { + emc_update_mista_rxintr(emc); + } +} + +static void emc_halt_tx(NPCM7xxEMCState *emc, uint32_t mista_flag) +{ + emc->tx_active = false; + emc_set_mista(emc, mista_flag); +} + +static void emc_halt_rx(NPCM7xxEMCState *emc, uint32_t mista_flag) +{ + emc->rx_active = false; + emc_set_mista(emc, mista_flag); +} + +static void emc_set_next_tx_descriptor(NPCM7xxEMCState *emc, + const NPCM7xxEMCTxDesc *tx_desc, + uint32_t desc_addr) +{ + /* Update the current descriptor, if only to reset the owner flag. */ + if (emc_write_tx_desc(tx_desc, desc_addr)) { + /* + * We just read it so this shouldn't generally happen. + * Error already reported. + */ + emc_set_mista(emc, REG_MISTA_TXBERR); + } + emc->regs[REG_CTXDSA] = TX_DESC_NTXDSA(tx_desc->ntxdsa); +} + +static void emc_set_next_rx_descriptor(NPCM7xxEMCState *emc, + const NPCM7xxEMCRxDesc *rx_desc, + uint32_t desc_addr) +{ + /* Update the current descriptor, if only to reset the owner flag. */ + if (emc_write_rx_desc(rx_desc, desc_addr)) { + /* + * We just read it so this shouldn't generally happen. + * Error already reported. + */ + emc_set_mista(emc, REG_MISTA_RXBERR); + } + emc->regs[REG_CRXDSA] = RX_DESC_NRXDSA(rx_desc->nrxdsa); +} + +static void emc_try_send_next_packet(NPCM7xxEMCState *emc) +{ + /* Working buffer for sending out packets. Most packets fit in this. */ +#define TX_BUFFER_SIZE 2048 + uint8_t tx_send_buffer[TX_BUFFER_SIZE]; + uint32_t desc_addr = TX_DESC_NTXDSA(emc->regs[REG_CTXDSA]); + NPCM7xxEMCTxDesc tx_desc; + uint32_t next_buf_addr, length; + uint8_t *buf; + g_autofree uint8_t *malloced_buf = NULL; + + if (emc_read_tx_desc(desc_addr, &tx_desc)) { + /* Error reading descriptor, already reported. */ + emc_halt_tx(emc, REG_MISTA_TXBERR); + emc_update_tx_irq(emc); + return; + } + + /* Nothing we can do if we don't own the descriptor. */ + if (!(tx_desc.flags & TX_DESC_FLAG_OWNER_MASK)) { + trace_npcm7xx_emc_cpu_owned_desc(desc_addr); + emc_halt_tx(emc, REG_MISTA_TDU); + emc_update_tx_irq(emc); + return; + } + + /* Give the descriptor back regardless of what happens. */ + tx_desc.flags &= ~TX_DESC_FLAG_OWNER_MASK; + tx_desc.status_and_length &= 0xffff; + + /* + * Despite the h/w documentation saying the tx buffer is word aligned, + * the linux driver does not word align the buffer. There is value in not + * aligning the buffer: See the description of NET_IP_ALIGN in linux + * kernel sources. + */ + next_buf_addr = tx_desc.txbsa; + emc->regs[REG_CTXBSA] = next_buf_addr; + length = TX_DESC_PKT_LEN(tx_desc.status_and_length); + buf = &tx_send_buffer[0]; + + if (length > sizeof(tx_send_buffer)) { + malloced_buf = g_malloc(length); + buf = malloced_buf; + } + + if (dma_memory_read(&address_space_memory, next_buf_addr, buf, length)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to read packet @ 0x%x\n", + __func__, next_buf_addr); + emc_set_mista(emc, REG_MISTA_TXBERR); + emc_set_next_tx_descriptor(emc, &tx_desc, desc_addr); + emc_update_tx_irq(emc); + trace_npcm7xx_emc_tx_done(emc->regs[REG_CTXDSA]); + return; + } + + if ((tx_desc.flags & TX_DESC_FLAG_PADEN) && (length < MIN_PACKET_LENGTH)) { + memset(buf + length, 0, MIN_PACKET_LENGTH - length); + length = MIN_PACKET_LENGTH; + } + + /* N.B. emc_receive can get called here. */ + qemu_send_packet(qemu_get_queue(emc->nic), buf, length); + trace_npcm7xx_emc_sent_packet(length); + + tx_desc.status_and_length |= TX_DESC_STATUS_TXCP; + if (tx_desc.flags & TX_DESC_FLAG_INTEN) { + emc_set_mista(emc, REG_MISTA_TXCP); + } + if (emc->regs[REG_MISTA] & emc->regs[REG_MIEN] & REG_MISTA_TXINTR) { + tx_desc.status_and_length |= TX_DESC_STATUS_TXINTR; + } + + emc_set_next_tx_descriptor(emc, &tx_desc, desc_addr); + emc_update_tx_irq(emc); + trace_npcm7xx_emc_tx_done(emc->regs[REG_CTXDSA]); +} + +static bool emc_can_receive(NetClientState *nc) +{ + NPCM7xxEMCState *emc = NPCM7XX_EMC(qemu_get_nic_opaque(nc)); + + bool can_receive = emc->rx_active; + trace_npcm7xx_emc_can_receive(can_receive); + return can_receive; +} + +/* If result is false then *fail_reason contains the reason. */ +static bool emc_receive_filter1(NPCM7xxEMCState *emc, const uint8_t *buf, + size_t len, const char **fail_reason) +{ + eth_pkt_types_e pkt_type = get_eth_packet_type(PKT_GET_ETH_HDR(buf)); + + switch (pkt_type) { + case ETH_PKT_BCAST: + if (emc->regs[REG_CAMCMR] & REG_CAMCMR_CCAM) { + return true; + } else { + *fail_reason = "Broadcast packet disabled"; + return !!(emc->regs[REG_CAMCMR] & REG_CAMCMR_ABP); + } + case ETH_PKT_MCAST: + if (emc->regs[REG_CAMCMR] & REG_CAMCMR_CCAM) { + return true; + } else { + *fail_reason = "Multicast packet disabled"; + return !!(emc->regs[REG_CAMCMR] & REG_CAMCMR_AMP); + } + case ETH_PKT_UCAST: { + bool matches; + if (emc->regs[REG_CAMCMR] & REG_CAMCMR_AUP) { + return true; + } + matches = ((emc->regs[REG_CAMCMR] & REG_CAMCMR_ECMP) && + /* We only support one CAM register, CAM0. */ + (emc->regs[REG_CAMEN] & (1 << 0)) && + memcmp(buf, emc->conf.macaddr.a, ETH_ALEN) == 0); + if (emc->regs[REG_CAMCMR] & REG_CAMCMR_CCAM) { + *fail_reason = "MACADDR matched, comparison complemented"; + return !matches; + } else { + *fail_reason = "MACADDR didn't match"; + return matches; + } + } + default: + g_assert_not_reached(); + } +} + +static bool emc_receive_filter(NPCM7xxEMCState *emc, const uint8_t *buf, + size_t len) +{ + const char *fail_reason = NULL; + bool ok = emc_receive_filter1(emc, buf, len, &fail_reason); + if (!ok) { + trace_npcm7xx_emc_packet_filtered_out(fail_reason); + } + return ok; +} + +static ssize_t emc_receive(NetClientState *nc, const uint8_t *buf, size_t len1) +{ + NPCM7xxEMCState *emc = NPCM7XX_EMC(qemu_get_nic_opaque(nc)); + const uint32_t len = len1; + size_t max_frame_len; + bool long_frame; + uint32_t desc_addr; + NPCM7xxEMCRxDesc rx_desc; + uint32_t crc; + uint8_t *crc_ptr; + uint32_t buf_addr; + + trace_npcm7xx_emc_receiving_packet(len); + + if (!emc_can_receive(nc)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Unexpected packet\n", __func__); + return -1; + } + + if (len < ETH_HLEN || + /* Defensive programming: drop unsupportable large packets. */ + len > 0xffff - CRC_LENGTH) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Dropped frame of %u bytes\n", + __func__, len); + return len; + } + + /* + * DENI is set if EMC received the Length/Type field of the incoming + * packet, so it will be set regardless of what happens next. + */ + emc_set_mista(emc, REG_MISTA_DENI); + + if (!emc_receive_filter(emc, buf, len)) { + emc_update_rx_irq(emc); + return len; + } + + /* Huge frames (> DMARFC) are dropped. */ + max_frame_len = REG_DMARFC_RXMS(emc->regs[REG_DMARFC]); + if (len + CRC_LENGTH > max_frame_len) { + trace_npcm7xx_emc_packet_dropped(len); + emc_set_mista(emc, REG_MISTA_DFOI); + emc_update_rx_irq(emc); + return len; + } + + /* + * Long Frames (> MAX_ETH_FRAME_SIZE) are also dropped, unless MCMDR.ALP + * is set. + */ + long_frame = false; + if (len + CRC_LENGTH > MAX_ETH_FRAME_SIZE) { + if (emc->regs[REG_MCMDR] & REG_MCMDR_ALP) { + long_frame = true; + } else { + trace_npcm7xx_emc_packet_dropped(len); + emc_set_mista(emc, REG_MISTA_PTLE); + emc_update_rx_irq(emc); + return len; + } + } + + desc_addr = RX_DESC_NRXDSA(emc->regs[REG_CRXDSA]); + if (emc_read_rx_desc(desc_addr, &rx_desc)) { + /* Error reading descriptor, already reported. */ + emc_halt_rx(emc, REG_MISTA_RXBERR); + emc_update_rx_irq(emc); + return len; + } + + /* Nothing we can do if we don't own the descriptor. */ + if (!(rx_desc.status_and_length & RX_DESC_STATUS_OWNER_MASK)) { + trace_npcm7xx_emc_cpu_owned_desc(desc_addr); + emc_halt_rx(emc, REG_MISTA_RDU); + emc_update_rx_irq(emc); + return len; + } + + crc = 0; + crc_ptr = (uint8_t *) &crc; + if (!(emc->regs[REG_MCMDR] & REG_MCMDR_SPCRC)) { + crc = cpu_to_be32(crc32(~0, buf, len)); + } + + /* Give the descriptor back regardless of what happens. */ + rx_desc.status_and_length &= ~RX_DESC_STATUS_OWNER_MASK; + + buf_addr = rx_desc.rxbsa; + emc->regs[REG_CRXBSA] = buf_addr; + if (dma_memory_write(&address_space_memory, buf_addr, buf, len) || + (!(emc->regs[REG_MCMDR] & REG_MCMDR_SPCRC) && + dma_memory_write(&address_space_memory, buf_addr + len, crc_ptr, + 4))) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bus error writing packet\n", + __func__); + emc_set_mista(emc, REG_MISTA_RXBERR); + emc_set_next_rx_descriptor(emc, &rx_desc, desc_addr); + emc_update_rx_irq(emc); + trace_npcm7xx_emc_rx_done(emc->regs[REG_CRXDSA]); + return len; + } + + trace_npcm7xx_emc_received_packet(len); + + /* Note: We've already verified len+4 <= 0xffff. */ + rx_desc.status_and_length = len; + if (!(emc->regs[REG_MCMDR] & REG_MCMDR_SPCRC)) { + rx_desc.status_and_length += 4; + } + rx_desc.status_and_length |= RX_DESC_STATUS_RXGD; + emc_set_mista(emc, REG_MISTA_RXGD); + + if (emc->regs[REG_MISTA] & emc->regs[REG_MIEN] & REG_MISTA_RXINTR) { + rx_desc.status_and_length |= RX_DESC_STATUS_RXINTR; + } + if (long_frame) { + rx_desc.status_and_length |= RX_DESC_STATUS_PTLE; + } + + emc_set_next_rx_descriptor(emc, &rx_desc, desc_addr); + emc_update_rx_irq(emc); + trace_npcm7xx_emc_rx_done(emc->regs[REG_CRXDSA]); + return len; +} + +static void emc_try_receive_next_packet(NPCM7xxEMCState *emc) +{ + if (emc_can_receive(qemu_get_queue(emc->nic))) { + qemu_flush_queued_packets(qemu_get_queue(emc->nic)); + } +} + +static uint64_t npcm7xx_emc_read(void *opaque, hwaddr offset, unsigned size) +{ + NPCM7xxEMCState *emc = opaque; + uint32_t reg = offset / sizeof(uint32_t); + uint32_t result; + + if (reg >= NPCM7XX_NUM_EMC_REGS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid offset 0x%04" HWADDR_PRIx "\n", + __func__, offset); + return 0; + } + + switch (reg) { + case REG_MIID: + /* + * We don't implement MII. For determinism, always return zero as + * writes record the last value written for debugging purposes. + */ + qemu_log_mask(LOG_UNIMP, "%s: Read of MIID, returning 0\n", __func__); + result = 0; + break; + case REG_TSDR: + case REG_RSDR: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Read of write-only reg, %s/%d\n", + __func__, emc_reg_name(reg), reg); + return 0; + default: + result = emc->regs[reg]; + break; + } + + trace_npcm7xx_emc_reg_read(emc->emc_num, result, emc_reg_name(reg), reg); + return result; +} + +static void npcm7xx_emc_write(void *opaque, hwaddr offset, + uint64_t v, unsigned size) +{ + NPCM7xxEMCState *emc = opaque; + uint32_t reg = offset / sizeof(uint32_t); + uint32_t value = v; + + g_assert(size == sizeof(uint32_t)); + + if (reg >= NPCM7XX_NUM_EMC_REGS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid offset 0x%04" HWADDR_PRIx "\n", + __func__, offset); + return; + } + + trace_npcm7xx_emc_reg_write(emc->emc_num, emc_reg_name(reg), reg, value); + + switch (reg) { + case REG_CAMCMR: + emc->regs[reg] = value; + break; + case REG_CAMEN: + /* Only CAM0 is supported, don't pretend otherwise. */ + if (value & ~1) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Only CAM0 is supported, cannot enable others" + ": 0x%x\n", + __func__, value); + } + emc->regs[reg] = value & 1; + break; + case REG_CAMM_BASE + 0: + emc->regs[reg] = value; + emc->conf.macaddr.a[0] = value >> 24; + emc->conf.macaddr.a[1] = value >> 16; + emc->conf.macaddr.a[2] = value >> 8; + emc->conf.macaddr.a[3] = value >> 0; + break; + case REG_CAML_BASE + 0: + emc->regs[reg] = value; + emc->conf.macaddr.a[4] = value >> 24; + emc->conf.macaddr.a[5] = value >> 16; + break; + case REG_MCMDR: { + uint32_t prev; + if (value & REG_MCMDR_SWR) { + emc_soft_reset(emc); + /* On h/w the reset happens over multiple cycles. For now KISS. */ + break; + } + prev = emc->regs[reg]; + emc->regs[reg] = value; + /* Update tx state. */ + if (!(prev & REG_MCMDR_TXON) && + (value & REG_MCMDR_TXON)) { + emc->regs[REG_CTXDSA] = emc->regs[REG_TXDLSA]; + /* + * Linux kernel turns TX on with CPU still holding descriptor, + * which suggests we should wait for a write to TSDR before trying + * to send a packet: so we don't send one here. + */ + } else if ((prev & REG_MCMDR_TXON) && + !(value & REG_MCMDR_TXON)) { + emc->regs[REG_MGSTA] |= REG_MGSTA_TXHA; + } + if (!(value & REG_MCMDR_TXON)) { + emc_halt_tx(emc, 0); + } + /* Update rx state. */ + if (!(prev & REG_MCMDR_RXON) && + (value & REG_MCMDR_RXON)) { + emc->regs[REG_CRXDSA] = emc->regs[REG_RXDLSA]; + } else if ((prev & REG_MCMDR_RXON) && + !(value & REG_MCMDR_RXON)) { + emc->regs[REG_MGSTA] |= REG_MGSTA_RXHA; + } + if (!(value & REG_MCMDR_RXON)) { + emc_halt_rx(emc, 0); + } + break; + } + case REG_TXDLSA: + case REG_RXDLSA: + case REG_DMARFC: + case REG_MIID: + emc->regs[reg] = value; + break; + case REG_MIEN: + emc->regs[reg] = value; + emc_update_irq_from_reg_change(emc); + break; + case REG_MISTA: + /* Clear the bits that have 1 in "value". */ + emc->regs[reg] &= ~value; + emc_update_irq_from_reg_change(emc); + break; + case REG_MGSTA: + /* Clear the bits that have 1 in "value". */ + emc->regs[reg] &= ~value; + break; + case REG_TSDR: + if (emc->regs[REG_MCMDR] & REG_MCMDR_TXON) { + emc->tx_active = true; + /* Keep trying to send packets until we run out. */ + while (emc->tx_active) { + emc_try_send_next_packet(emc); + } + } + break; + case REG_RSDR: + if (emc->regs[REG_MCMDR] & REG_MCMDR_RXON) { + emc->rx_active = true; + emc_try_receive_next_packet(emc); + } + break; + case REG_MIIDA: + emc->regs[reg] = value & ~REG_MIIDA_BUSY; + break; + case REG_MRPC: + case REG_MRPCC: + case REG_MREPC: + case REG_CTXDSA: + case REG_CTXBSA: + case REG_CRXDSA: + case REG_CRXBSA: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Write to read-only reg %s/%d\n", + __func__, emc_reg_name(reg), reg); + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Write to unimplemented reg %s/%d\n", + __func__, emc_reg_name(reg), reg); + break; + } +} + +static const struct MemoryRegionOps npcm7xx_emc_ops = { + .read = npcm7xx_emc_read, + .write = npcm7xx_emc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void emc_cleanup(NetClientState *nc) +{ + /* Nothing to do yet. */ +} + +static NetClientInfo net_npcm7xx_emc_info = { + .type = NET_CLIENT_DRIVER_NIC, + .size = sizeof(NICState), + .can_receive = emc_can_receive, + .receive = emc_receive, + .cleanup = emc_cleanup, + .link_status_changed = emc_set_link, +}; + +static void npcm7xx_emc_realize(DeviceState *dev, Error **errp) +{ + NPCM7xxEMCState *emc = NPCM7XX_EMC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(emc); + + memory_region_init_io(&emc->iomem, OBJECT(emc), &npcm7xx_emc_ops, emc, + TYPE_NPCM7XX_EMC, 4 * KiB); + sysbus_init_mmio(sbd, &emc->iomem); + sysbus_init_irq(sbd, &emc->tx_irq); + sysbus_init_irq(sbd, &emc->rx_irq); + + qemu_macaddr_default_if_unset(&emc->conf.macaddr); + emc->nic = qemu_new_nic(&net_npcm7xx_emc_info, &emc->conf, + object_get_typename(OBJECT(dev)), dev->id, emc); + qemu_format_nic_info_str(qemu_get_queue(emc->nic), emc->conf.macaddr.a); +} + +static void npcm7xx_emc_unrealize(DeviceState *dev) +{ + NPCM7xxEMCState *emc = NPCM7XX_EMC(dev); + + qemu_del_nic(emc->nic); +} + +static const VMStateDescription vmstate_npcm7xx_emc = { + .name = TYPE_NPCM7XX_EMC, + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(emc_num, NPCM7xxEMCState), + VMSTATE_UINT32_ARRAY(regs, NPCM7xxEMCState, NPCM7XX_NUM_EMC_REGS), + VMSTATE_BOOL(tx_active, NPCM7xxEMCState), + VMSTATE_BOOL(rx_active, NPCM7xxEMCState), + VMSTATE_END_OF_LIST(), + }, +}; + +static Property npcm7xx_emc_properties[] = { + DEFINE_NIC_PROPERTIES(NPCM7xxEMCState, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void npcm7xx_emc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); + dc->desc = "NPCM7xx EMC Controller"; + dc->realize = npcm7xx_emc_realize; + dc->unrealize = npcm7xx_emc_unrealize; + dc->reset = npcm7xx_emc_reset; + dc->vmsd = &vmstate_npcm7xx_emc; + device_class_set_props(dc, npcm7xx_emc_properties); +} + +static const TypeInfo npcm7xx_emc_info = { + .name = TYPE_NPCM7XX_EMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxEMCState), + .class_init = npcm7xx_emc_class_init, +}; + +static void npcm7xx_emc_register_type(void) +{ + type_register_static(&npcm7xx_emc_info); +} + +type_init(npcm7xx_emc_register_type) diff --git a/hw/net/meson.build b/hw/net/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/net/meson.build +++ b/hw/net/meson.build @@ -XXX,XX +XXX,XX @@ softmmu_ss.add(when: 'CONFIG_I82596_COMMON', if_true: files('i82596.c')) softmmu_ss.add(when: 'CONFIG_SUNHME', if_true: files('sunhme.c')) softmmu_ss.add(when: 'CONFIG_FTGMAC100', if_true: files('ftgmac100.c')) softmmu_ss.add(when: 'CONFIG_SUNGEM', if_true: files('sungem.c')) +softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_emc.c')) softmmu_ss.add(when: 'CONFIG_ETRAXFS', if_true: files('etraxfs_eth.c')) softmmu_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_fec.c')) diff --git a/hw/net/trace-events b/hw/net/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/net/trace-events +++ b/hw/net/trace-events @@ -XXX,XX +XXX,XX @@ imx_fec_receive_last(int last) "rx frame flags 0x%04x" imx_enet_receive(size_t size) "len %zu" imx_enet_receive_len(uint64_t addr, int len) "rx_bd 0x%"PRIx64" length %d" imx_enet_receive_last(int last) "rx frame flags 0x%04x" + +# npcm7xx_emc.c +npcm7xx_emc_reset(int emc_num) "Resetting emc%d" +npcm7xx_emc_update_tx_irq(int level) "Setting tx irq to %d" +npcm7xx_emc_update_rx_irq(int level) "Setting rx irq to %d" +npcm7xx_emc_set_mista(uint32_t flags) "ORing 0x%x into MISTA" +npcm7xx_emc_cpu_owned_desc(uint32_t addr) "Can't process cpu-owned descriptor @0x%x" +npcm7xx_emc_sent_packet(uint32_t len) "Sent %u byte packet" +npcm7xx_emc_tx_done(uint32_t ctxdsa) "TX done, CTXDSA=0x%x" +npcm7xx_emc_can_receive(int can_receive) "Can receive: %d" +npcm7xx_emc_packet_filtered_out(const char* fail_reason) "Packet filtered out: %s" +npcm7xx_emc_packet_dropped(uint32_t len) "%u byte packet dropped" +npcm7xx_emc_receiving_packet(uint32_t len) "Receiving %u byte packet" +npcm7xx_emc_received_packet(uint32_t len) "Received %u byte packet" +npcm7xx_emc_rx_done(uint32_t crxdsa) "RX done, CRXDSA=0x%x" +npcm7xx_emc_reg_read(int emc_num, uint32_t result, const char *name, int regno) "emc%d: 0x%x = reg[%s/%d]" +npcm7xx_emc_reg_write(int emc_num, const char *name, int regno, uint32_t value) "emc%d: reg[%s/%d] = 0x%x" -- 2.20.1
From: Doug Evans <dje@google.com> This is a 10/100 ethernet device that has several features. Only the ones needed by the Linux driver have been implemented. See npcm7xx_emc.c for a list of unimplemented features. Reviewed-by: Hao Wu <wuhaotsh@google.com> Reviewed-by: Avi Fishman <avi.fishman@nuvoton.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Doug Evans <dje@google.com> Message-id: 20210213002520.1374134-3-dje@google.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- docs/system/arm/nuvoton.rst | 3 ++- include/hw/arm/npcm7xx.h | 2 ++ hw/arm/npcm7xx.c | 50 +++++++++++++++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/docs/system/arm/nuvoton.rst b/docs/system/arm/nuvoton.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/nuvoton.rst +++ b/docs/system/arm/nuvoton.rst @@ -XXX,XX +XXX,XX @@ Supported devices * Analog to Digital Converter (ADC) * Pulse Width Modulation (PWM) * SMBus controller (SMBF) + * Ethernet controller (EMC) Missing devices --------------- @@ -XXX,XX +XXX,XX @@ Missing devices * Shared memory (SHM) * eSPI slave interface - * Ethernet controllers (GMAC and EMC) + * Ethernet controller (GMAC) * USB device (USBD) * Peripheral SPI controller (PSPI) * SD/MMC host diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/npcm7xx.h +++ b/include/hw/arm/npcm7xx.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/npcm7xx_gcr.h" #include "hw/misc/npcm7xx_pwm.h" #include "hw/misc/npcm7xx_rng.h" +#include "hw/net/npcm7xx_emc.h" #include "hw/nvram/npcm7xx_otp.h" #include "hw/timer/npcm7xx_timer.h" #include "hw/ssi/npcm7xx_fiu.h" @@ -XXX,XX +XXX,XX @@ typedef struct NPCM7xxState { EHCISysBusState ehci; OHCISysBusState ohci; NPCM7xxFIUState fiu[2]; + NPCM7xxEMCState emc[2]; } NPCM7xxState; #define TYPE_NPCM7XX "npcm7xx" diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/npcm7xx.c +++ b/hw/arm/npcm7xx.c @@ -XXX,XX +XXX,XX @@ enum NPCM7xxInterrupt { NPCM7XX_UART1_IRQ, NPCM7XX_UART2_IRQ, NPCM7XX_UART3_IRQ, + NPCM7XX_EMC1RX_IRQ = 15, + NPCM7XX_EMC1TX_IRQ, NPCM7XX_TIMER0_IRQ = 32, /* Timer Module 0 */ NPCM7XX_TIMER1_IRQ, NPCM7XX_TIMER2_IRQ, @@ -XXX,XX +XXX,XX @@ enum NPCM7xxInterrupt { NPCM7XX_SMBUS15_IRQ, NPCM7XX_PWM0_IRQ = 93, /* PWM module 0 */ NPCM7XX_PWM1_IRQ, /* PWM module 1 */ + NPCM7XX_EMC2RX_IRQ = 114, + NPCM7XX_EMC2TX_IRQ, NPCM7XX_GPIO0_IRQ = 116, NPCM7XX_GPIO1_IRQ, NPCM7XX_GPIO2_IRQ, @@ -XXX,XX +XXX,XX @@ static const hwaddr npcm7xx_smbus_addr[] = { 0xf008f000, }; +/* Register base address for each EMC Module */ +static const hwaddr npcm7xx_emc_addr[] = { + 0xf0825000, + 0xf0826000, +}; + static const struct { hwaddr regs_addr; uint32_t unconnected_pins; @@ -XXX,XX +XXX,XX @@ static void npcm7xx_init(Object *obj) for (i = 0; i < ARRAY_SIZE(s->pwm); i++) { object_initialize_child(obj, "pwm[*]", &s->pwm[i], TYPE_NPCM7XX_PWM); } + + for (i = 0; i < ARRAY_SIZE(s->emc); i++) { + object_initialize_child(obj, "emc[*]", &s->emc[i], TYPE_NPCM7XX_EMC); + } } static void npcm7xx_realize(DeviceState *dev, Error **errp) @@ -XXX,XX +XXX,XX @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(sbd, i, npcm7xx_irq(s, NPCM7XX_PWM0_IRQ + i)); } + /* + * EMC Modules. Cannot fail. + * The mapping of the device to its netdev backend works as follows: + * emc[i] = nd_table[i] + * This works around the inability to specify the netdev property for the + * emc device: it's not pluggable and thus the -device option can't be + * used. + */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_emc_addr) != ARRAY_SIZE(s->emc)); + QEMU_BUILD_BUG_ON(ARRAY_SIZE(s->emc) != 2); + for (i = 0; i < ARRAY_SIZE(s->emc); i++) { + s->emc[i].emc_num = i; + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->emc[i]); + if (nd_table[i].used) { + qemu_check_nic_model(&nd_table[i], TYPE_NPCM7XX_EMC); + qdev_set_nic_properties(DEVICE(sbd), &nd_table[i]); + } + /* + * The device exists regardless of whether it's connected to a QEMU + * netdev backend. So always instantiate it even if there is no + * backend. + */ + sysbus_realize(sbd, &error_abort); + sysbus_mmio_map(sbd, 0, npcm7xx_emc_addr[i]); + int tx_irq = i == 0 ? NPCM7XX_EMC1TX_IRQ : NPCM7XX_EMC2TX_IRQ; + int rx_irq = i == 0 ? NPCM7XX_EMC1RX_IRQ : NPCM7XX_EMC2RX_IRQ; + /* + * N.B. The values for the second argument sysbus_connect_irq are + * chosen to match the registration order in npcm7xx_emc_realize. + */ + sysbus_connect_irq(sbd, 0, npcm7xx_irq(s, tx_irq)); + sysbus_connect_irq(sbd, 1, npcm7xx_irq(s, rx_irq)); + } + /* * Flash Interface Unit (FIU). Can fail if incorrect number of chip selects * specified, but this is a programming error. @@ -XXX,XX +XXX,XX @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) create_unimplemented_device("npcm7xx.vcd", 0xf0810000, 64 * KiB); create_unimplemented_device("npcm7xx.ece", 0xf0820000, 8 * KiB); create_unimplemented_device("npcm7xx.vdma", 0xf0822000, 8 * KiB); - create_unimplemented_device("npcm7xx.emc1", 0xf0825000, 4 * KiB); - create_unimplemented_device("npcm7xx.emc2", 0xf0826000, 4 * KiB); create_unimplemented_device("npcm7xx.usbd[0]", 0xf0830000, 4 * KiB); create_unimplemented_device("npcm7xx.usbd[1]", 0xf0831000, 4 * KiB); create_unimplemented_device("npcm7xx.usbd[2]", 0xf0832000, 4 * KiB); -- 2.20.1
From: Doug Evans <dje@google.com> Reviewed-by: Hao Wu <wuhaotsh@google.com> Reviewed-by: Avi Fishman <avi.fishman@nuvoton.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Doug Evans <dje@google.com> Message-id: 20210213002520.1374134-4-dje@google.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- tests/qtest/npcm7xx_emc-test.c | 862 +++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 1 + 2 files changed, 863 insertions(+) create mode 100644 tests/qtest/npcm7xx_emc-test.c diff --git a/tests/qtest/npcm7xx_emc-test.c b/tests/qtest/npcm7xx_emc-test.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/npcm7xx_emc-test.c @@ -XXX,XX +XXX,XX @@ +/* + * QTests for Nuvoton NPCM7xx EMC Modules. + * + * 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-common.h" +#include "libqos/libqos.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qnum.h" +#include "qemu/bitops.h" +#include "qemu/iov.h" + +/* Name of the emc device. */ +#define TYPE_NPCM7XX_EMC "npcm7xx-emc" + +/* Timeout for various operations, in seconds. */ +#define TIMEOUT_SECONDS 10 + +/* Address in memory of the descriptor. */ +#define DESC_ADDR (1 << 20) /* 1 MiB */ + +/* Address in memory of the data packet. */ +#define DATA_ADDR (DESC_ADDR + 4096) + +#define CRC_LENGTH 4 + +#define NUM_TX_DESCRIPTORS 3 +#define NUM_RX_DESCRIPTORS 2 + +/* Size of tx,rx test buffers. */ +#define TX_DATA_LEN 64 +#define RX_DATA_LEN 64 + +#define TX_STEP_COUNT 10000 +#define RX_STEP_COUNT 10000 + +/* 32-bit register indices. */ +typedef enum NPCM7xxPWMRegister { + /* Control registers. */ + REG_CAMCMR, + REG_CAMEN, + + /* There are 16 CAMn[ML] registers. */ + REG_CAMM_BASE, + REG_CAML_BASE, + + REG_TXDLSA = 0x22, + REG_RXDLSA, + REG_MCMDR, + REG_MIID, + REG_MIIDA, + REG_FFTCR, + REG_TSDR, + REG_RSDR, + REG_DMARFC, + REG_MIEN, + + /* Status registers. */ + REG_MISTA, + REG_MGSTA, + REG_MPCNT, + REG_MRPC, + REG_MRPCC, + REG_MREPC, + REG_DMARFS, + REG_CTXDSA, + REG_CTXBSA, + REG_CRXDSA, + REG_CRXBSA, + + NPCM7XX_NUM_EMC_REGS, +} NPCM7xxPWMRegister; + +enum { NUM_CAMML_REGS = 16 }; + +/* REG_CAMCMR fields */ +/* Enable CAM Compare */ +#define REG_CAMCMR_ECMP (1 << 4) +/* Accept Unicast Packet */ +#define REG_CAMCMR_AUP (1 << 0) + +/* REG_MCMDR fields */ +/* Software Reset */ +#define REG_MCMDR_SWR (1 << 24) +/* Frame Transmission On */ +#define REG_MCMDR_TXON (1 << 8) +/* Accept Long Packet */ +#define REG_MCMDR_ALP (1 << 1) +/* Frame Reception On */ +#define REG_MCMDR_RXON (1 << 0) + +/* REG_MIEN fields */ +/* Enable Transmit Completion Interrupt */ +#define REG_MIEN_ENTXCP (1 << 18) +/* Enable Transmit Interrupt */ +#define REG_MIEN_ENTXINTR (1 << 16) +/* Enable Receive Good Interrupt */ +#define REG_MIEN_ENRXGD (1 << 4) +/* ENable Receive Interrupt */ +#define REG_MIEN_ENRXINTR (1 << 0) + +/* REG_MISTA fields */ +/* Transmit Bus Error Interrupt */ +#define REG_MISTA_TXBERR (1 << 24) +/* Transmit Descriptor Unavailable Interrupt */ +#define REG_MISTA_TDU (1 << 23) +/* Transmit Completion Interrupt */ +#define REG_MISTA_TXCP (1 << 18) +/* Transmit Interrupt */ +#define REG_MISTA_TXINTR (1 << 16) +/* Receive Bus Error Interrupt */ +#define REG_MISTA_RXBERR (1 << 11) +/* Receive Descriptor Unavailable Interrupt */ +#define REG_MISTA_RDU (1 << 10) +/* DMA Early Notification Interrupt */ +#define REG_MISTA_DENI (1 << 9) +/* Maximum Frame Length Interrupt */ +#define REG_MISTA_DFOI (1 << 8) +/* Receive Good Interrupt */ +#define REG_MISTA_RXGD (1 << 4) +/* Packet Too Long Interrupt */ +#define REG_MISTA_PTLE (1 << 3) +/* Receive Interrupt */ +#define REG_MISTA_RXINTR (1 << 0) + +typedef struct NPCM7xxEMCTxDesc NPCM7xxEMCTxDesc; +typedef struct NPCM7xxEMCRxDesc NPCM7xxEMCRxDesc; + +struct NPCM7xxEMCTxDesc { + uint32_t flags; + uint32_t txbsa; + uint32_t status_and_length; + uint32_t ntxdsa; +}; + +struct NPCM7xxEMCRxDesc { + uint32_t status_and_length; + uint32_t rxbsa; + uint32_t reserved; + uint32_t nrxdsa; +}; + +/* NPCM7xxEMCTxDesc.flags values */ +/* Owner: 0 = cpu, 1 = emc */ +#define TX_DESC_FLAG_OWNER_MASK (1 << 31) +/* Transmit interrupt enable */ +#define TX_DESC_FLAG_INTEN (1 << 2) + +/* NPCM7xxEMCTxDesc.status_and_length values */ +/* Transmission complete */ +#define TX_DESC_STATUS_TXCP (1 << 19) +/* Transmit interrupt */ +#define TX_DESC_STATUS_TXINTR (1 << 16) + +/* NPCM7xxEMCRxDesc.status_and_length values */ +/* Owner: 0b00 = cpu, 0b10 = emc */ +#define RX_DESC_STATUS_OWNER_SHIFT 30 +#define RX_DESC_STATUS_OWNER_MASK 0xc0000000 +/* Frame Reception Complete */ +#define RX_DESC_STATUS_RXGD (1 << 20) +/* Packet too long */ +#define RX_DESC_STATUS_PTLE (1 << 19) +/* Receive Interrupt */ +#define RX_DESC_STATUS_RXINTR (1 << 16) + +#define RX_DESC_PKT_LEN(word) ((uint32_t) (word) & 0xffff) + +typedef struct EMCModule { + int rx_irq; + int tx_irq; + uint64_t base_addr; +} EMCModule; + +typedef struct TestData { + const EMCModule *module; +} TestData; + +static const EMCModule emc_module_list[] = { + { + .rx_irq = 15, + .tx_irq = 16, + .base_addr = 0xf0825000 + }, + { + .rx_irq = 114, + .tx_irq = 115, + .base_addr = 0xf0826000 + } +}; + +/* Returns the index of the EMC module. */ +static int emc_module_index(const EMCModule *mod) +{ + ptrdiff_t diff = mod - emc_module_list; + + g_assert_true(diff >= 0 && diff < ARRAY_SIZE(emc_module_list)); + + return diff; +} + +static void packet_test_clear(void *sockets) +{ + int *test_sockets = sockets; + + close(test_sockets[0]); + g_free(test_sockets); +} + +static int *packet_test_init(int module_num, GString *cmd_line) +{ + int *test_sockets = g_new(int, 2); + int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, test_sockets); + g_assert_cmpint(ret, != , -1); + + /* + * KISS and use -nic. We specify two nics (both emc{0,1}) because there's + * currently no way to specify only emc1: The driver implicitly relies on + * emc[i] == nd_table[i]. + */ + if (module_num == 0) { + g_string_append_printf(cmd_line, + " -nic socket,fd=%d,model=" TYPE_NPCM7XX_EMC " " + " -nic user,model=" TYPE_NPCM7XX_EMC " ", + test_sockets[1]); + } else { + g_string_append_printf(cmd_line, + " -nic user,model=" TYPE_NPCM7XX_EMC " " + " -nic socket,fd=%d,model=" TYPE_NPCM7XX_EMC " ", + test_sockets[1]); + } + + g_test_queue_destroy(packet_test_clear, test_sockets); + return test_sockets; +} + +static uint32_t emc_read(QTestState *qts, const EMCModule *mod, + NPCM7xxPWMRegister regno) +{ + return qtest_readl(qts, mod->base_addr + regno * sizeof(uint32_t)); +} + +static void emc_write(QTestState *qts, const EMCModule *mod, + NPCM7xxPWMRegister regno, uint32_t value) +{ + qtest_writel(qts, mod->base_addr + regno * sizeof(uint32_t), value); +} + +static void emc_read_tx_desc(QTestState *qts, uint32_t addr, + NPCM7xxEMCTxDesc *desc) +{ + qtest_memread(qts, addr, desc, sizeof(*desc)); + desc->flags = le32_to_cpu(desc->flags); + desc->txbsa = le32_to_cpu(desc->txbsa); + desc->status_and_length = le32_to_cpu(desc->status_and_length); + desc->ntxdsa = le32_to_cpu(desc->ntxdsa); +} + +static void emc_write_tx_desc(QTestState *qts, const NPCM7xxEMCTxDesc *desc, + uint32_t addr) +{ + NPCM7xxEMCTxDesc le_desc; + + le_desc.flags = cpu_to_le32(desc->flags); + le_desc.txbsa = cpu_to_le32(desc->txbsa); + le_desc.status_and_length = cpu_to_le32(desc->status_and_length); + le_desc.ntxdsa = cpu_to_le32(desc->ntxdsa); + qtest_memwrite(qts, addr, &le_desc, sizeof(le_desc)); +} + +static void emc_read_rx_desc(QTestState *qts, uint32_t addr, + NPCM7xxEMCRxDesc *desc) +{ + qtest_memread(qts, addr, desc, sizeof(*desc)); + desc->status_and_length = le32_to_cpu(desc->status_and_length); + desc->rxbsa = le32_to_cpu(desc->rxbsa); + desc->reserved = le32_to_cpu(desc->reserved); + desc->nrxdsa = le32_to_cpu(desc->nrxdsa); +} + +static void emc_write_rx_desc(QTestState *qts, const NPCM7xxEMCRxDesc *desc, + uint32_t addr) +{ + NPCM7xxEMCRxDesc le_desc; + + le_desc.status_and_length = cpu_to_le32(desc->status_and_length); + le_desc.rxbsa = cpu_to_le32(desc->rxbsa); + le_desc.reserved = cpu_to_le32(desc->reserved); + le_desc.nrxdsa = cpu_to_le32(desc->nrxdsa); + qtest_memwrite(qts, addr, &le_desc, sizeof(le_desc)); +} + +/* + * Reset the EMC module. + * The module must be reset before, e.g., TXDLSA,RXDLSA are changed. + */ +static bool emc_soft_reset(QTestState *qts, const EMCModule *mod) +{ + uint32_t val; + uint64_t end_time; + + emc_write(qts, mod, REG_MCMDR, REG_MCMDR_SWR); + + /* + * Wait for device to reset as the linux driver does. + * During reset the AHB reads 0 for all registers. So first wait for + * something that resets to non-zero, and then wait for SWR becoming 0. + */ + end_time = g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SECOND; + + do { + qtest_clock_step(qts, 100); + val = emc_read(qts, mod, REG_FFTCR); + } while (val == 0 && g_get_monotonic_time() < end_time); + if (val != 0) { + do { + qtest_clock_step(qts, 100); + val = emc_read(qts, mod, REG_MCMDR); + if ((val & REG_MCMDR_SWR) == 0) { + /* + * N.B. The CAMs have been reset here, so macaddr matching of + * incoming packets will not work. + */ + return true; + } + } while (g_get_monotonic_time() < end_time); + } + + g_message("%s: Timeout expired", __func__); + return false; +} + +/* Check emc registers are reset to default value. */ +static void test_init(gconstpointer test_data) +{ + const TestData *td = test_data; + const EMCModule *mod = td->module; + QTestState *qts = qtest_init("-machine quanta-gsj"); + int i; + +#define CHECK_REG(regno, value) \ + do { \ + g_assert_cmphex(emc_read(qts, mod, (regno)), ==, (value)); \ + } while (0) + + CHECK_REG(REG_CAMCMR, 0); + CHECK_REG(REG_CAMEN, 0); + CHECK_REG(REG_TXDLSA, 0xfffffffc); + CHECK_REG(REG_RXDLSA, 0xfffffffc); + CHECK_REG(REG_MCMDR, 0); + CHECK_REG(REG_MIID, 0); + CHECK_REG(REG_MIIDA, 0x00900000); + CHECK_REG(REG_FFTCR, 0x0101); + CHECK_REG(REG_DMARFC, 0x0800); + CHECK_REG(REG_MIEN, 0); + CHECK_REG(REG_MISTA, 0); + CHECK_REG(REG_MGSTA, 0); + CHECK_REG(REG_MPCNT, 0x7fff); + CHECK_REG(REG_MRPC, 0); + CHECK_REG(REG_MRPCC, 0); + CHECK_REG(REG_MREPC, 0); + CHECK_REG(REG_DMARFS, 0); + CHECK_REG(REG_CTXDSA, 0); + CHECK_REG(REG_CTXBSA, 0); + CHECK_REG(REG_CRXDSA, 0); + CHECK_REG(REG_CRXBSA, 0); + +#undef CHECK_REG + + for (i = 0; i < NUM_CAMML_REGS; ++i) { + g_assert_cmpuint(emc_read(qts, mod, REG_CAMM_BASE + i * 2), ==, + 0); + g_assert_cmpuint(emc_read(qts, mod, REG_CAML_BASE + i * 2), ==, + 0); + } + + qtest_quit(qts); +} + +static bool emc_wait_irq(QTestState *qts, const EMCModule *mod, int step, + bool is_tx) +{ + uint64_t end_time = + g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SECOND; + + do { + if (qtest_get_irq(qts, is_tx ? mod->tx_irq : mod->rx_irq)) { + return true; + } + qtest_clock_step(qts, step); + } while (g_get_monotonic_time() < end_time); + + g_message("%s: Timeout expired", __func__); + return false; +} + +static bool emc_wait_mista(QTestState *qts, const EMCModule *mod, int step, + uint32_t flag) +{ + uint64_t end_time = + g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SECOND; + + do { + uint32_t mista = emc_read(qts, mod, REG_MISTA); + if (mista & flag) { + return true; + } + qtest_clock_step(qts, step); + } while (g_get_monotonic_time() < end_time); + + g_message("%s: Timeout expired", __func__); + return false; +} + +static bool wait_socket_readable(int fd) +{ + fd_set read_fds; + struct timeval tv; + int rv; + + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + tv.tv_sec = TIMEOUT_SECONDS; + tv.tv_usec = 0; + rv = select(fd + 1, &read_fds, NULL, NULL, &tv); + if (rv == -1) { + perror("select"); + } else if (rv == 0) { + g_message("%s: Timeout expired", __func__); + } + return rv == 1; +} + +/* Initialize *desc (in host endian format). */ +static void init_tx_desc(NPCM7xxEMCTxDesc *desc, size_t count, + uint32_t desc_addr) +{ + g_assert(count >= 2); + memset(&desc[0], 0, sizeof(*desc) * count); + /* Leave the last one alone, owned by the cpu -> stops transmission. */ + for (size_t i = 0; i < count - 1; ++i) { + desc[i].flags = + (TX_DESC_FLAG_OWNER_MASK | /* owner = 1: emc */ + TX_DESC_FLAG_INTEN | + 0 | /* crc append = 0 */ + 0 /* padding enable = 0 */); + desc[i].status_and_length = + (0 | /* collision count = 0 */ + 0 | /* SQE = 0 */ + 0 | /* PAU = 0 */ + 0 | /* TXHA = 0 */ + 0 | /* LC = 0 */ + 0 | /* TXABT = 0 */ + 0 | /* NCS = 0 */ + 0 | /* EXDEF = 0 */ + 0 | /* TXCP = 0 */ + 0 | /* DEF = 0 */ + 0 | /* TXINTR = 0 */ + 0 /* length filled in later */); + desc[i].ntxdsa = desc_addr + (i + 1) * sizeof(*desc); + } +} + +static void enable_tx(QTestState *qts, const EMCModule *mod, + const NPCM7xxEMCTxDesc *desc, size_t count, + uint32_t desc_addr, uint32_t mien_flags) +{ + /* Write the descriptors to guest memory. */ + for (size_t i = 0; i < count; ++i) { + emc_write_tx_desc(qts, desc + i, desc_addr + i * sizeof(*desc)); + } + + /* Trigger sending the packet. */ + /* The module must be reset before changing TXDLSA. */ + g_assert(emc_soft_reset(qts, mod)); + emc_write(qts, mod, REG_TXDLSA, desc_addr); + emc_write(qts, mod, REG_CTXDSA, ~0); + emc_write(qts, mod, REG_MIEN, REG_MIEN_ENTXCP | mien_flags); + { + uint32_t mcmdr = emc_read(qts, mod, REG_MCMDR); + mcmdr |= REG_MCMDR_TXON; + emc_write(qts, mod, REG_MCMDR, mcmdr); + } + + /* Prod the device to send the packet. */ + emc_write(qts, mod, REG_TSDR, 1); +} + +static void emc_send_verify1(QTestState *qts, const EMCModule *mod, int fd, + bool with_irq, uint32_t desc_addr, + uint32_t next_desc_addr, + const char *test_data, int test_size) +{ + NPCM7xxEMCTxDesc result_desc; + uint32_t expected_mask, expected_value, recv_len; + int ret; + char buffer[TX_DATA_LEN]; + + g_assert(wait_socket_readable(fd)); + + /* Read the descriptor back. */ + emc_read_tx_desc(qts, desc_addr, &result_desc); + /* Descriptor should be owned by cpu now. */ + g_assert((result_desc.flags & TX_DESC_FLAG_OWNER_MASK) == 0); + /* Test the status bits, ignoring the length field. */ + expected_mask = 0xffff << 16; + expected_value = TX_DESC_STATUS_TXCP; + if (with_irq) { + expected_value |= TX_DESC_STATUS_TXINTR; + } + g_assert_cmphex((result_desc.status_and_length & expected_mask), ==, + expected_value); + + /* Check data sent to the backend. */ + recv_len = ~0; + ret = qemu_recv(fd, &recv_len, sizeof(recv_len), MSG_DONTWAIT); + g_assert_cmpint(ret, == , sizeof(recv_len)); + + g_assert(wait_socket_readable(fd)); + memset(buffer, 0xff, sizeof(buffer)); + ret = qemu_recv(fd, buffer, test_size, MSG_DONTWAIT); + g_assert_cmpmem(buffer, ret, test_data, test_size); +} + +static void emc_send_verify(QTestState *qts, const EMCModule *mod, int fd, + bool with_irq) +{ + NPCM7xxEMCTxDesc desc[NUM_TX_DESCRIPTORS]; + uint32_t desc_addr = DESC_ADDR; + static const char test1_data[] = "TEST1"; + static const char test2_data[] = "Testing 1 2 3 ..."; + uint32_t data1_addr = DATA_ADDR; + uint32_t data2_addr = data1_addr + sizeof(test1_data); + bool got_tdu; + uint32_t end_desc_addr; + + /* Prepare test data buffer. */ + qtest_memwrite(qts, data1_addr, test1_data, sizeof(test1_data)); + qtest_memwrite(qts, data2_addr, test2_data, sizeof(test2_data)); + + init_tx_desc(&desc[0], NUM_TX_DESCRIPTORS, desc_addr); + desc[0].txbsa = data1_addr; + desc[0].status_and_length |= sizeof(test1_data); + desc[1].txbsa = data2_addr; + desc[1].status_and_length |= sizeof(test2_data); + + enable_tx(qts, mod, &desc[0], NUM_TX_DESCRIPTORS, desc_addr, + with_irq ? REG_MIEN_ENTXINTR : 0); + + /* + * It's problematic to observe the interrupt for each packet. + * Instead just wait until all the packets go out. + */ + got_tdu = false; + while (!got_tdu) { + if (with_irq) { + g_assert_true(emc_wait_irq(qts, mod, TX_STEP_COUNT, + /*is_tx=*/true)); + } else { + g_assert_true(emc_wait_mista(qts, mod, TX_STEP_COUNT, + REG_MISTA_TXINTR)); + } + got_tdu = !!(emc_read(qts, mod, REG_MISTA) & REG_MISTA_TDU); + /* If we don't have TDU yet, reset the interrupt. */ + if (!got_tdu) { + emc_write(qts, mod, REG_MISTA, + emc_read(qts, mod, REG_MISTA) & 0xffff0000); + } + } + + end_desc_addr = desc_addr + 2 * sizeof(desc[0]); + g_assert_cmphex(emc_read(qts, mod, REG_CTXDSA), ==, end_desc_addr); + g_assert_cmphex(emc_read(qts, mod, REG_MISTA), ==, + REG_MISTA_TXCP | REG_MISTA_TXINTR | REG_MISTA_TDU); + + emc_send_verify1(qts, mod, fd, with_irq, + desc_addr, end_desc_addr, + test1_data, sizeof(test1_data)); + emc_send_verify1(qts, mod, fd, with_irq, + desc_addr + sizeof(desc[0]), end_desc_addr, + test2_data, sizeof(test2_data)); +} + +/* Initialize *desc (in host endian format). */ +static void init_rx_desc(NPCM7xxEMCRxDesc *desc, size_t count, + uint32_t desc_addr, uint32_t data_addr) +{ + g_assert_true(count >= 2); + memset(desc, 0, sizeof(*desc) * count); + desc[0].rxbsa = data_addr; + desc[0].status_and_length = + (0b10 << RX_DESC_STATUS_OWNER_SHIFT | /* owner = 10: emc */ + 0 | /* RP = 0 */ + 0 | /* ALIE = 0 */ + 0 | /* RXGD = 0 */ + 0 | /* PTLE = 0 */ + 0 | /* CRCE = 0 */ + 0 | /* RXINTR = 0 */ + 0 /* length (filled in later) */); + /* Leave the last one alone, owned by the cpu -> stops transmission. */ + desc[0].nrxdsa = desc_addr + sizeof(*desc); +} + +static void enable_rx(QTestState *qts, const EMCModule *mod, + const NPCM7xxEMCRxDesc *desc, size_t count, + uint32_t desc_addr, uint32_t mien_flags, + uint32_t mcmdr_flags) +{ + /* + * Write the descriptor to guest memory. + * FWIW, IWBN if the docs said the buffer needs to be at least DMARFC + * bytes. + */ + for (size_t i = 0; i < count; ++i) { + emc_write_rx_desc(qts, desc + i, desc_addr + i * sizeof(*desc)); + } + + /* Trigger receiving the packet. */ + /* The module must be reset before changing RXDLSA. */ + g_assert(emc_soft_reset(qts, mod)); + emc_write(qts, mod, REG_RXDLSA, desc_addr); + emc_write(qts, mod, REG_MIEN, REG_MIEN_ENRXGD | mien_flags); + + /* + * We don't know what the device's macaddr is, so just accept all + * unicast packets (AUP). + */ + emc_write(qts, mod, REG_CAMCMR, REG_CAMCMR_AUP); + emc_write(qts, mod, REG_CAMEN, 1 << 0); + { + uint32_t mcmdr = emc_read(qts, mod, REG_MCMDR); + mcmdr |= REG_MCMDR_RXON | mcmdr_flags; + emc_write(qts, mod, REG_MCMDR, mcmdr); + } + + /* Prod the device to accept a packet. */ + emc_write(qts, mod, REG_RSDR, 1); +} + +static void emc_recv_verify(QTestState *qts, const EMCModule *mod, int fd, + bool with_irq) +{ + NPCM7xxEMCRxDesc desc[NUM_RX_DESCRIPTORS]; + uint32_t desc_addr = DESC_ADDR; + uint32_t data_addr = DATA_ADDR; + int ret; + uint32_t expected_mask, expected_value; + NPCM7xxEMCRxDesc result_desc; + + /* Prepare test data buffer. */ + const char test[RX_DATA_LEN] = "TEST"; + int len = htonl(sizeof(test)); + const struct iovec iov[] = { + { + .iov_base = &len, + .iov_len = sizeof(len), + },{ + .iov_base = (char *) test, + .iov_len = sizeof(test), + }, + }; + + /* + * Reset the device BEFORE sending a test packet, otherwise the packet + * may get swallowed by an active device of an earlier test. + */ + init_rx_desc(&desc[0], NUM_RX_DESCRIPTORS, desc_addr, data_addr); + enable_rx(qts, mod, &desc[0], NUM_RX_DESCRIPTORS, desc_addr, + with_irq ? REG_MIEN_ENRXINTR : 0, 0); + + /* Send test packet to device's socket. */ + ret = iov_send(fd, iov, 2, 0, sizeof(len) + sizeof(test)); + g_assert_cmpint(ret, == , sizeof(test) + sizeof(len)); + + /* Wait for RX interrupt. */ + if (with_irq) { + g_assert_true(emc_wait_irq(qts, mod, RX_STEP_COUNT, /*is_tx=*/false)); + } else { + g_assert_true(emc_wait_mista(qts, mod, RX_STEP_COUNT, REG_MISTA_RXGD)); + } + + g_assert_cmphex(emc_read(qts, mod, REG_CRXDSA), ==, + desc_addr + sizeof(desc[0])); + + expected_mask = 0xffff; + expected_value = (REG_MISTA_DENI | + REG_MISTA_RXGD | + REG_MISTA_RXINTR); + g_assert_cmphex((emc_read(qts, mod, REG_MISTA) & expected_mask), + ==, expected_value); + + /* Read the descriptor back. */ + emc_read_rx_desc(qts, desc_addr, &result_desc); + /* Descriptor should be owned by cpu now. */ + g_assert((result_desc.status_and_length & RX_DESC_STATUS_OWNER_MASK) == 0); + /* Test the status bits, ignoring the length field. */ + expected_mask = 0xffff << 16; + expected_value = RX_DESC_STATUS_RXGD; + if (with_irq) { + expected_value |= RX_DESC_STATUS_RXINTR; + } + g_assert_cmphex((result_desc.status_and_length & expected_mask), ==, + expected_value); + g_assert_cmpint(RX_DESC_PKT_LEN(result_desc.status_and_length), ==, + RX_DATA_LEN + CRC_LENGTH); + + { + char buffer[RX_DATA_LEN]; + qtest_memread(qts, data_addr, buffer, sizeof(buffer)); + g_assert_cmpstr(buffer, == , "TEST"); + } +} + +static void emc_test_ptle(QTestState *qts, const EMCModule *mod, int fd) +{ + NPCM7xxEMCRxDesc desc[NUM_RX_DESCRIPTORS]; + uint32_t desc_addr = DESC_ADDR; + uint32_t data_addr = DATA_ADDR; + int ret; + NPCM7xxEMCRxDesc result_desc; + uint32_t expected_mask, expected_value; + + /* Prepare test data buffer. */ +#define PTLE_DATA_LEN 1600 + char test_data[PTLE_DATA_LEN]; + int len = htonl(sizeof(test_data)); + const struct iovec iov[] = { + { + .iov_base = &len, + .iov_len = sizeof(len), + },{ + .iov_base = (char *) test_data, + .iov_len = sizeof(test_data), + }, + }; + memset(test_data, 42, sizeof(test_data)); + + /* + * Reset the device BEFORE sending a test packet, otherwise the packet + * may get swallowed by an active device of an earlier test. + */ + init_rx_desc(&desc[0], NUM_RX_DESCRIPTORS, desc_addr, data_addr); + enable_rx(qts, mod, &desc[0], NUM_RX_DESCRIPTORS, desc_addr, + REG_MIEN_ENRXINTR, REG_MCMDR_ALP); + + /* Send test packet to device's socket. */ + ret = iov_send(fd, iov, 2, 0, sizeof(len) + sizeof(test_data)); + g_assert_cmpint(ret, == , sizeof(test_data) + sizeof(len)); + + /* Wait for RX interrupt. */ + g_assert_true(emc_wait_irq(qts, mod, RX_STEP_COUNT, /*is_tx=*/false)); + + /* Read the descriptor back. */ + emc_read_rx_desc(qts, desc_addr, &result_desc); + /* Descriptor should be owned by cpu now. */ + g_assert((result_desc.status_and_length & RX_DESC_STATUS_OWNER_MASK) == 0); + /* Test the status bits, ignoring the length field. */ + expected_mask = 0xffff << 16; + expected_value = (RX_DESC_STATUS_RXGD | + RX_DESC_STATUS_PTLE | + RX_DESC_STATUS_RXINTR); + g_assert_cmphex((result_desc.status_and_length & expected_mask), ==, + expected_value); + g_assert_cmpint(RX_DESC_PKT_LEN(result_desc.status_and_length), ==, + PTLE_DATA_LEN + CRC_LENGTH); + + { + char buffer[PTLE_DATA_LEN]; + qtest_memread(qts, data_addr, buffer, sizeof(buffer)); + g_assert(memcmp(buffer, test_data, PTLE_DATA_LEN) == 0); + } +} + +static void test_tx(gconstpointer test_data) +{ + const TestData *td = test_data; + GString *cmd_line = g_string_new("-machine quanta-gsj"); + int *test_sockets = packet_test_init(emc_module_index(td->module), + cmd_line); + QTestState *qts = qtest_init(cmd_line->str); + + /* + * TODO: For pedantic correctness test_sockets[0] should be closed after + * the fork and before the exec, but that will require some harness + * improvements. + */ + close(test_sockets[1]); + /* Defensive programming */ + test_sockets[1] = -1; + + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + + emc_send_verify(qts, td->module, test_sockets[0], /*with_irq=*/false); + emc_send_verify(qts, td->module, test_sockets[0], /*with_irq=*/true); + + qtest_quit(qts); +} + +static void test_rx(gconstpointer test_data) +{ + const TestData *td = test_data; + GString *cmd_line = g_string_new("-machine quanta-gsj"); + int *test_sockets = packet_test_init(emc_module_index(td->module), + cmd_line); + QTestState *qts = qtest_init(cmd_line->str); + + /* + * TODO: For pedantic correctness test_sockets[0] should be closed after + * the fork and before the exec, but that will require some harness + * improvements. + */ + close(test_sockets[1]); + /* Defensive programming */ + test_sockets[1] = -1; + + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + + emc_recv_verify(qts, td->module, test_sockets[0], /*with_irq=*/false); + emc_recv_verify(qts, td->module, test_sockets[0], /*with_irq=*/true); + emc_test_ptle(qts, td->module, test_sockets[0]); + + qtest_quit(qts); +} + +static void emc_add_test(const char *name, const TestData* td, + GTestDataFunc fn) +{ + g_autofree char *full_name = g_strdup_printf( + "npcm7xx_emc/emc[%d]/%s", emc_module_index(td->module), name); + qtest_add_data_func(full_name, td, fn); +} +#define add_test(name, td) emc_add_test(#name, td, test_##name) + +int main(int argc, char **argv) +{ + TestData test_data_list[ARRAY_SIZE(emc_module_list)]; + + g_test_init(&argc, &argv, NULL); + + for (int i = 0; i < ARRAY_SIZE(emc_module_list); ++i) { + TestData *td = &test_data_list[i]; + + td->module = &emc_module_list[i]; + + add_test(init, td); + add_test(tx, td); + add_test(rx, td); + } + + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -XXX,XX +XXX,XX @@ qtests_sparc64 = \ qtests_npcm7xx = \ ['npcm7xx_adc-test', + 'npcm7xx_emc-test', 'npcm7xx_gpio-test', 'npcm7xx_pwm-test', 'npcm7xx_rng-test', -- 2.20.1
Hi; here's another arm pullreq; main parts here are the new i.MX 8MM EVK board, and a lot of refactoring. thanks -- PMM The following changes since commit aa15257174da180c6a8a9d58f87319cfe61c5520: Merge tag 'pbouvier/pr/plugins-20260424' of https://gitlab.com/p-b-o/qemu into staging (2026-04-25 10:22:04 -0400) are available in the Git repository at: https://gitlab.com/pm215/qemu.git tags/pull-target-arm-20260427 for you to fetch changes up to 4575da5ecb7a27544aa6da1f78e700e8100ceaea: target/arm: report register in WFIT syndromes (2026-04-27 11:46:34 +0100) ---------------------------------------------------------------- target-arm queue: docs/system: add FEAT_AA32 and FEAT_AA64 to emulation list hw/arm: Add the i.MX 8MM EVK(Evaluation Kit) board target/arm: Build M-profile helper code once only hw/arm: Remove hw_error() for the unimplemented CM_LMBUSCNT register hw: Move ARM_SYSCTL_GPIO definitions to arm sysctl specific header target/arm: Allow 'aarch64=off' to be set for TCG CPUs target/arm: Allow some sysregs to not have to be an exact match for migration hw/arm/raspi4b: NOP all DTB nodes when removing unimplemented devices hw/arm/fsl-imx6ul: Implement LCDIF display device target/arm: Refactor syndrome value code to use registerfields target/arm: Report the register in WFxT syndromes ---------------------------------------------------------------- Alex Bennée (24): docs/system: add FEAT_AA32 and FEAT_AA64 to emulation list target/arm: migrate basic syndrome helpers to registerfields target/arm: migrate system/cp trap syndromes to registerfields target/arm: migrate FP/SIMD trap syndromes to registerfields target/arm: migrate eret trap syndromes to registerfields target/arm: migrate SME trap syndromes to registerfields target/arm: migrate PAC trap syndromes to registerfields target/arm: migrate BTI trap syndromes to registerfields target/arm: migrate BXJ trap syndromes to registerfields target/arm: migrate Granule Protection traps to registerfields target/arm: migrate fault syndromes to registerfields target/arm: migrate debug syndromes to registerfields target/arm: migrate wfx syndromes to registerfields target/arm: migrate gcs syndromes to registerfields target/arm: migrate memory op syndromes to registerfields target/arm: migrate check_hcr_el2_trap to use syndrome helper target/arm: use syndrome helpers in arm_cpu_do_interrupt_aarch32_hyp target/arm: use syndrome helpers to set SAME_EL EC bit target/arm: make whpx use syndrome helpers for decode target/arm: make hvf use syndrome helpers for decode target/arm: use syndrome helpers in merge_syn_data_abort target/arm: use syndrome helpers to query VNCR bit target/arm: remove old syndrome defines target/arm: report register in WFIT syndromes Eric Auger (7): target/arm/cpu: Introduce the infrastructure for cpreg migration tolerances target/arm/machine: Handle ToleranceNotOnBothEnds migration tolerances target/arm/machine: Handle ToleranceOnlySrcTestValue migration tolerance target/arm/cpu64: Mitigate migration failures due to spurious TCR_EL1, PIRE0_EL1 and PIR_EL1 target/arm/cpu64: Define cpreg migration tolerance for KVM_REG_ARM_VENDOR_HYP_BMAP_2 target/arm/helper: Define cpreg migration tolerance for DGBDTR_EL0 Revert "target/arm: Reinstate bogus AArch32 DBGDTRTX register for migration compat" Gaurav Sharma (15): hw/arm: Add the i.MX 8MM EVK(Evaluation Kit) board hw/misc/imx8mp_analog: Add property to analog device hw/arm/fsl-imx8mm: Add Analog device IP to iMX8MM SOC hw/arm/fsl-imx8mm: Add Clock Control Module IP to iMX8MM hw/arm/fsl-imx8mm: Implemented support for SNVS hw/arm/fsl-imx8mm: Adding support for USDHC storage controllers hw/arm/fsl-imx8mm: Add PCIe support hw/arm/fsl-imx8mm: Add GPIO controllers hw/arm/fsl-imx8mm: Adding support for I2C emulation hw/arm/fsl-imx8mm: Adding support for SPI controller hw/arm/fsl-imx8mm: Adding support for Watchdog Timers hw/arm/fsl-imx8mm: Adding support for General Purpose Timers hw/arm/fsl-imx8mm: Adding support for ENET ethernet controller hw/arm/fsl-imx8mm: Adding support for USB controller hw/arm/fsl-imx8mm: Adding functional testing of iMX8MM emulation Osama Abdelkader (1): hw/arm/raspi4b: NOP all DTB nodes when removing unimplemented devices Peter Maydell (4): hw: Move ARM_SYSCTL_GPIO definitions to arm sysctl specific header target/arm: Clear AArch64 ID regs from ARMISARegisters if AArch64 disabled target/arm: Allow 'aarch64=off' to be set for TCG CPUs tests/functional/aarch64: Add basic test of TCG aarch64=off Philippe Mathieu-Daudé (9): target/arm: Ignore endianness when setting MTE tags target/arm: Explode MO_TExx -> MO_TE | MO_xx target/arm: Hoist MO_TE into mve_advance_vpt() target/arm: Hoist MO_TE into MVE DO_VSTR() macro target/arm: Introduce mo_endian() helper target/arm: Replace MO_TE -> mo_endian() for MVE helpers target/arm: Compile mve_helper.c once target/arm: Replace MO_TE -> mo_endian() for Cortex-M helpers target/arm: Compile m_helper.c once Thomas Huth (1): hw/arm: Remove hw_error() for the unimplemented CM_LMBUSCNT register Yucai Liu (2): hw/display: Add i.MX6UL LCDIF device model hw/arm/fsl-imx6ul: Wire in the LCDIF device model MAINTAINERS | 16 +- docs/system/arm/cpu-features.rst | 10 +- docs/system/arm/emulation.rst | 2 + docs/system/arm/{imx8mp-evk.rst => imx8m.rst} | 49 +- docs/system/target-arm.rst | 2 +- hw/arm/Kconfig | 25 + hw/arm/fsl-imx6ul.c | 12 +- hw/arm/fsl-imx8mm.c | 704 ++++++++++++++++++++++ hw/arm/imx8mm-evk.c | 130 ++++ hw/arm/integratorcp.c | 3 - hw/arm/meson.build | 2 + hw/arm/raspi4b.c | 10 +- hw/arm/realview.c | 2 +- hw/arm/vexpress.c | 2 +- hw/arm/virt.c | 1 - hw/display/Kconfig | 4 + hw/display/imx6ul_lcdif.c | 453 ++++++++++++++ hw/display/meson.build | 1 + hw/misc/arm_sysctl.c | 2 +- hw/misc/imx8mp_analog.c | 12 +- hw/timer/imx_gpt.c | 26 + hw/vmapple/vmapple.c | 1 - include/hw/arm/fsl-imx6ul.h | 4 +- include/hw/arm/fsl-imx8mm.h | 242 ++++++++ include/hw/arm/primecell.h | 12 - include/hw/display/imx6ul_lcdif.h | 37 ++ include/hw/misc/arm_sysctl.h | 16 + include/hw/misc/imx8mp_analog.h | 3 + include/hw/timer/imx_gpt.h | 2 + target/arm/cpu-features.h | 5 + target/arm/cpu.c | 153 ++++- target/arm/cpu.h | 4 +- target/arm/cpu64.c | 39 ++ target/arm/debug_helper.c | 29 - target/arm/helper.c | 27 +- target/arm/hvf/hvf.c | 14 +- target/arm/internals.h | 60 ++ target/arm/machine.c | 33 +- target/arm/syndrome.h | 595 ++++++++++++++---- target/arm/tcg/debug.c | 2 +- target/arm/tcg/helper-a64.c | 2 +- target/arm/tcg/helper-defs.h | 2 +- target/arm/tcg/m_helper.c | 8 +- target/arm/tcg/meson.build | 6 +- target/arm/tcg/mve_helper.c | 102 ++-- target/arm/tcg/op_helper.c | 7 +- target/arm/tcg/tlb_helper.c | 6 +- target/arm/tcg/translate-a64.c | 2 +- target/arm/tcg/vfp_helper.c | 5 +- target/arm/trace-events | 2 + target/arm/whpx/whpx-all.c | 13 +- tests/functional/aarch64/meson.build | 3 + tests/functional/aarch64/test_imx8mm_evk.py | 69 +++ tests/functional/aarch64/test_virt_aarch64_off.py | 37 ++ tests/qtest/arm-cpu-features.c | 8 +- 55 files changed, 2743 insertions(+), 275 deletions(-) rename docs/system/arm/{imx8mp-evk.rst => imx8m.rst} (58%) create mode 100644 hw/arm/fsl-imx8mm.c create mode 100644 hw/arm/imx8mm-evk.c create mode 100644 hw/display/imx6ul_lcdif.c create mode 100644 include/hw/arm/fsl-imx8mm.h delete mode 100644 include/hw/arm/primecell.h create mode 100644 include/hw/display/imx6ul_lcdif.h create mode 100644 include/hw/misc/arm_sysctl.h create mode 100755 tests/functional/aarch64/test_imx8mm_evk.py create mode 100755 tests/functional/aarch64/test_virt_aarch64_off.py
From: Alex Bennée <alex.bennee@linaro.org> This is just a documentation tweak as we already support both. FEAT_AA32 implies FEAT_AA32EL0. FEAT_AA64 implies FEAT_AA64EL[0123]. This is however useful if you are using emulation.rst as a source of truth of what QEMU emulates and when cross checking with Features.json from Arm. Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260421093506.616307-1-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- docs/system/arm/emulation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/emulation.rst +++ b/docs/system/arm/emulation.rst @@ -XXX,XX +XXX,XX @@ QEMU's TCG emulation includes support for the Armv5, Armv6, Armv7, Armv8 and Armv9 versions of the A-profile architecture. It also has support for the following architecture extensions: +- FEAT_AA32 (PE Support for AArch32) - FEAT_AA32BF16 (AArch32 BFloat16 instructions) - FEAT_AA32EL0 (Support for AArch32 at EL0) - FEAT_AA32EL1 (Support for AArch32 at EL1) @@ -XXX,XX +XXX,XX @@ the following architecture extensions: - FEAT_AA32EL3 (Support for AArch32 at EL3) - FEAT_AA32HPD (AArch32 hierarchical permission disables) - FEAT_AA32I8MM (AArch32 Int8 matrix multiplication instructions) +- FEAT_AA64 (PE uses AArch64 after last reboot) - FEAT_AA64EL0 (Support for AArch64 at EL0) - FEAT_AA64EL1 (Support for AArch64 at EL1) - FEAT_AA64EL2 (Support for AArch64 at EL2) -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Implemented CPUs, RAM, UARTs and Interrupt Controller Other peripherals are represented as TYPE_UNIMPLEMENTED_DEVICE Complete memory map of the SoC is provided. Set default RAM size to 2GB and default CPU count to 4 to match the real i.MX8MM EVK hardware configuration. Documentation is shared with imx8mp-evk to avoid duplication. Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> [PMM: fixed over-long lines in doc] Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- MAINTAINERS | 11 +- docs/system/arm/{imx8mp-evk.rst => imx8m.rst} | 49 ++- docs/system/target-arm.rst | 2 +- hw/arm/Kconfig | 12 + hw/arm/fsl-imx8mm.c | 377 ++++++++++++++++++ hw/arm/imx8mm-evk.c | 112 ++++++ hw/arm/meson.build | 2 + include/hw/arm/fsl-imx8mm.h | 158 ++++++++ 8 files changed, 709 insertions(+), 14 deletions(-) rename docs/system/arm/{imx8mp-evk.rst => imx8m.rst} (58%) create mode 100644 hw/arm/fsl-imx8mm.c create mode 100644 hw/arm/imx8mm-evk.c create mode 100644 include/hw/arm/fsl-imx8mm.h diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: hw/pci-host/designware.c F: include/hw/pci-host/designware.h F: docs/system/arm/mcimx7d-sabre.rst +MCIMX8MM-EVK / iMX8MM +M: Gaurav Sharma <gaurav.sharma_7@nxp.com> +L: qemu-arm@nongnu.org +S: Maintained +F: hw/arm/fsl-imx8mm.c +F: hw/arm/imx8mm-evk.c +F: include/hw/arm/fsl-imx8mm.h +F: docs/system/arm/imx8m.rst + MCIMX8MP-EVK / i.MX8MP M: Bernhard Beschow <shentey@gmail.com> L: qemu-arm@nongnu.org @@ -XXX,XX +XXX,XX @@ F: hw/rtc/rs5c372.c F: include/hw/arm/fsl-imx8mp.h F: include/hw/misc/imx8mp_*.h F: include/hw/pci-host/fsl_imx8m_phy.h -F: docs/system/arm/imx8mp-evk.rst +F: docs/system/arm/imx8m.rst F: tests/functional/aarch64/test_imx8mp_evk.py F: tests/qtest/rs5c372-test.c diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8m.rst similarity index 58% rename from docs/system/arm/imx8mp-evk.rst rename to docs/system/arm/imx8m.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8m.rst @@ -XXX,XX +XXX,XX @@ -NXP i.MX 8M Plus Evaluation Kit (``imx8mp-evk``) -================================================ +NXP i.MX 8M Plus and i.MX 8M Mini Evaluation Kits (``imx8mp-evk``, ``imx8mm-evk``) +================================================================================== -The ``imx8mp-evk`` machine models the i.MX 8M Plus Evaluation Kit, based on an -i.MX 8M Plus SoC. +The ``imx8mp-evk`` and ``imx8mm-evk`` machine models the i.MX 8M Plus +and i.MX 8M Mini Evaluation Kits, based on i.MX 8M Plus and i.MX8M +Mini SoCs. Supported devices ----------------- -The ``imx8mp-evk`` machine implements the following devices: +The ``imx8mp-evk`` and ``imx8mm-evk`` machines implement the +following devices: * Up to 4 Cortex-A53 cores * Generic Interrupt Controller (GICv3) @@ -XXX,XX +XXX,XX @@ The ``imx8mp-evk`` machine implements the following devices: Boot options ------------ -The ``imx8mp-evk`` machine can start a Linux kernel directly using the standard -``-kernel`` functionality. +The ``imx8mp-evk`` and ``imx8mm-evk`` machines can start a Linux +kernel directly using the standard ``-kernel`` functionality. Direct Linux Kernel Boot '''''''''''''''''''''''' @@ -XXX,XX +XXX,XX @@ is to generate an image with Buildroot. Version 2024.11.1 is tested at the time of writing and involves two steps. First run the following commands in the toplevel directory of the Buildroot source tree: +For i.MX 8M Plus EVK: + .. code-block:: bash $ make freescale_imx8mpevk_defconfig $ make +For i.MX 8M Mini EVK: + +.. code-block:: bash + + $ make freescale_imx8mmevk_defconfig + $ make + Once finished successfully there is an ``output/image`` subfolder. Navigate into it and resize the SD card image to a power of two: @@ -XXX,XX +XXX,XX @@ it and resize the SD card image to a power of two: Now that everything is prepared the machine can be started as follows: +For i.MX 8M Plus EVK: + .. code-block:: bash $ qemu-system-aarch64 -M imx8mp-evk \ @@ -XXX,XX +XXX,XX @@ Now that everything is prepared the machine can be started as follows: -append "root=/dev/mmcblk2p2" \ -drive file=sdcard.img,if=sd,bus=2,format=raw,id=mmcblk2 +For i.MX 8M Mini EVK: + +.. code-block:: bash + + $ qemu-system-aarch64 -M imx8mm-evk -smp 4 -m 2G \ + -display none -serial null -serial stdio \ + -kernel Image \ + -dtb imx8mm-evk.dtb \ + -append "root=/dev/mmcblk2p2" \ + -drive file=sdcard.img,if=sd,bus=2,format=raw,id=mmcblk2 KVM Acceleration ---------------- @@ -XXX,XX +XXX,XX @@ To enable hardware-assisted acceleration via KVM, append ``-accel kvm`` to the command line. While this speeds up performance significantly, be aware of the following limitations: -* The ``imx8mp-evk`` machine is not included under the "virtualization use case" - of :doc:`QEMU's security policy </system/security>`. This means that you - should not trust that it can contain malicious guests, whether it is run - using TCG or KVM. If you don't trust your guests and you're relying on QEMU to - be the security boundary, you want to choose another machine such as ``virt``. +* The ``imx8mp-evk`` and ``imx8mm-evk`` machines are not included + under the "virtualization use case" of :doc:`QEMU's security + policy </system/security>`. This means that you should not trust that + it can contain malicious guests, whether it is run using TCG or KVM. + If you don't trust your guests and you're relying on QEMU to be the + security boundary, you want to choose another machine such as + ``virt``. * Rather than Cortex-A53 CPUs, the same CPU type as the host's will be used. This is a limitation of KVM and may not work with guests with a tight dependency on Cortex-A53. diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/target-arm.rst +++ b/docs/system/target-arm.rst @@ -XXX,XX +XXX,XX @@ Board-specific documentation arm/imx25-pdk arm/mcimx6ul-evk arm/mcimx7d-sabre - arm/imx8mp-evk + arm/imx8m arm/orangepi arm/raspi arm/collie diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MP_EVK depends on TCG select FSL_IMX8MP +config FSL_IMX8MM + bool + select ARM_GIC + select IMX + +config FSL_IMX8MM_EVK + bool + default y + depends on AARCH64 + depends on TCG + select FSL_IMX8MM + config ARM_SMMUV3_ACCEL bool depends on ARM_SMMUV3 diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ +/* + * i.MX 8MM SoC Implementation + * + * Based on hw/arm/fsl-imx6.c + * + * Copyright (c) 2025, NXP Semiconductors + * Author: Gaurav Sharma <gaurav.sharma_7@nxp.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * iMX8MM Reference Manual - https://www.nxp.com/products/i.MX8MMINI -> Documentation + */ + +#include "qemu/osdep.h" +#include "system/address-spaces.h" +#include "hw/arm/bsa.h" +#include "hw/arm/fsl-imx8mm.h" +#include "hw/misc/unimp.h" +#include "hw/core/boards.h" +#include "system/kvm.h" +#include "system/system.h" +#include "target/arm/cpu.h" +#include "target/arm/cpu-qom.h" +#include "target/arm/kvm_arm.h" +#include "qapi/error.h" +#include "qobject/qlist.h" + +static const struct { + hwaddr addr; + size_t size; + const char *name; +} fsl_imx8mm_memmap[] = { + [FSL_IMX8MM_RAM] = { FSL_IMX8MM_RAM_START, FSL_IMX8MM_RAM_SIZE_MAX, "ram" }, + [FSL_IMX8MM_DDR_PHY_BROADCAST] = { 0x3dc00000, 4 * MiB, "ddr_phy_broadcast" }, + [FSL_IMX8MM_DDR_PERF_MON] = { 0x3d800000, 4 * MiB, "ddr_perf_mon" }, + [FSL_IMX8MM_DDR_CTL] = { 0x3d400000, 4 * MiB, "ddr_ctl" }, + [FSL_IMX8MM_DDR_PHY] = { 0x3c000000, 16 * MiB, "ddr_phy" }, + [FSL_IMX8MM_GIC_DIST] = { 0x38800000, 512 * KiB, "gic_dist" }, + [FSL_IMX8MM_GIC_REDIST] = { 0x38880000, 512 * KiB, "gic_redist" }, + [FSL_IMX8MM_VPU] = { 0x38340000, 2 * MiB, "vpu" }, + [FSL_IMX8MM_VPU_BLK_CTRL] = { 0x38330000, 2 * MiB, "vpu_blk_ctrl" }, + [FSL_IMX8MM_VPU_G2_DECODER] = { 0x38310000, 1 * MiB, "vpu_g2_decoder" }, + [FSL_IMX8MM_VPU_G1_DECODER] = { 0x38300000, 1 * MiB, "vpu_g1_decoder" }, + [FSL_IMX8MM_USB2_OTG] = { 0x32e50200, 0x200, "usb2_otg" }, + [FSL_IMX8MM_USB2] = { 0x32e50000, 0x200, "usb2" }, + [FSL_IMX8MM_USB1_OTG] = { 0x32e40200, 0x200, "usb1_otg" }, + [FSL_IMX8MM_USB1] = { 0x32e40000, 0x200, "usb1" }, + [FSL_IMX8MM_GPU2D] = { 0x38000000, 64 * KiB, "gpu2d" }, + [FSL_IMX8MM_QSPI1_RX_BUFFER] = { 0x34000000, 32 * MiB, "qspi1_rx_buffer" }, + [FSL_IMX8MM_PCIE1] = { 0x33800000, 4 * MiB, "pcie1" }, + [FSL_IMX8MM_QSPI1_TX_BUFFER] = { 0x33008000, 32 * KiB, "qspi1_tx_buffer" }, + [FSL_IMX8MM_APBH_DMA] = { 0x33000000, 32 * KiB, "apbh_dma" }, + + /* AIPS-4 Begin */ + [FSL_IMX8MM_TZASC] = { 0x32f80000, 64 * KiB, "tzasc" }, + [FSL_IMX8MM_PCIE_PHY1] = { 0x32f00000, 64 * KiB, "pcie_phy1" }, + [FSL_IMX8MM_MEDIA_BLK_CTL] = { 0x32e28000, 256, "media_blk_ctl" }, + [FSL_IMX8MM_LCDIF] = { 0x32e00000, 64 * KiB, "lcdif" }, + [FSL_IMX8MM_MIPI_DSI] = { 0x32e10000, 64 * KiB, "mipi_dsi" }, + [FSL_IMX8MM_MIPI_CSI] = { 0x32e30000, 64 * KiB, "mipi_csi" }, + [FSL_IMX8MM_AIPS4_CONFIGURATION] = { 0x32df0000, 64 * KiB, "aips4_configuration" }, + /* AIPS-4 End */ + + [FSL_IMX8MM_INTERCONNECT] = { 0x32700000, 1 * MiB, "interconnect" }, + + /* AIPS-3 Begin */ + [FSL_IMX8MM_ENET1] = { 0x30be0000, 64 * KiB, "enet1" }, + [FSL_IMX8MM_SDMA1] = { 0x30bd0000, 64 * KiB, "sdma1" }, + [FSL_IMX8MM_QSPI] = { 0x30bb0000, 64 * KiB, "qspi" }, + [FSL_IMX8MM_USDHC3] = { 0x30b60000, 64 * KiB, "usdhc3" }, + [FSL_IMX8MM_USDHC2] = { 0x30b50000, 64 * KiB, "usdhc2" }, + [FSL_IMX8MM_USDHC1] = { 0x30b40000, 64 * KiB, "usdhc1" }, + [FSL_IMX8MM_SEMAPHORE_HS] = { 0x30ac0000, 64 * KiB, "semaphore_hs" }, + [FSL_IMX8MM_MU_B] = { 0x30ab0000, 64 * KiB, "mu_b" }, + [FSL_IMX8MM_MU_A] = { 0x30aa0000, 64 * KiB, "mu_a" }, + [FSL_IMX8MM_UART4] = { 0x30a60000, 64 * KiB, "uart4" }, + [FSL_IMX8MM_I2C4] = { 0x30a50000, 64 * KiB, "i2c4" }, + [FSL_IMX8MM_I2C3] = { 0x30a40000, 64 * KiB, "i2c3" }, + [FSL_IMX8MM_I2C2] = { 0x30a30000, 64 * KiB, "i2c2" }, + [FSL_IMX8MM_I2C1] = { 0x30a20000, 64 * KiB, "i2c1" }, + [FSL_IMX8MM_AIPS3_CONFIGURATION] = { 0x309f0000, 64 * KiB, "aips3_configuration" }, + [FSL_IMX8MM_CAAM] = { 0x30900000, 256 * KiB, "caam" }, + [FSL_IMX8MM_SPBA1] = { 0x308f0000, 64 * KiB, "spba1" }, + [FSL_IMX8MM_UART2] = { 0x30890000, 64 * KiB, "uart2" }, + [FSL_IMX8MM_UART3] = { 0x30880000, 64 * KiB, "uart3" }, + [FSL_IMX8MM_UART1] = { 0x30860000, 64 * KiB, "uart1" }, + [FSL_IMX8MM_ECSPI3] = { 0x30840000, 64 * KiB, "ecspi3" }, + [FSL_IMX8MM_ECSPI2] = { 0x30830000, 64 * KiB, "ecspi2" }, + [FSL_IMX8MM_ECSPI1] = { 0x30820000, 64 * KiB, "ecspi1" }, + /* AIPS-3 End */ + + /* AIPS-2 Begin */ + [FSL_IMX8MM_QOSC] = { 0x307f0000, 64 * KiB, "qosc" }, + [FSL_IMX8MM_PERFMON2] = { 0x307d0000, 64 * KiB, "perfmon2" }, + [FSL_IMX8MM_PERFMON1] = { 0x307c0000, 64 * KiB, "perfmon1" }, + [FSL_IMX8MM_GPT4] = { 0x30700000, 64 * KiB, "gpt4" }, + [FSL_IMX8MM_GPT5] = { 0x306f0000, 64 * KiB, "gpt5" }, + [FSL_IMX8MM_GPT6] = { 0x306e0000, 64 * KiB, "gpt6" }, + [FSL_IMX8MM_SYSCNT_CTRL] = { 0x306c0000, 64 * KiB, "syscnt_ctrl" }, + [FSL_IMX8MM_SYSCNT_CMP] = { 0x306b0000, 64 * KiB, "syscnt_cmp" }, + [FSL_IMX8MM_SYSCNT_RD] = { 0x306a0000, 64 * KiB, "syscnt_rd" }, + [FSL_IMX8MM_PWM4] = { 0x30690000, 64 * KiB, "pwm4" }, + [FSL_IMX8MM_PWM3] = { 0x30680000, 64 * KiB, "pwm3" }, + [FSL_IMX8MM_PWM2] = { 0x30670000, 64 * KiB, "pwm2" }, + [FSL_IMX8MM_PWM1] = { 0x30660000, 64 * KiB, "pwm1" }, + [FSL_IMX8MM_AIPS2_CONFIGURATION] = { 0x305f0000, 64 * KiB, "aips2_configuration" }, + /* AIPS-2 End */ + + /* AIPS-1 Begin */ + [FSL_IMX8MM_CSU] = { 0x303e0000, 64 * KiB, "csu" }, + [FSL_IMX8MM_RDC] = { 0x303d0000, 64 * KiB, "rdc" }, + [FSL_IMX8MM_SEMAPHORE2] = { 0x303c0000, 64 * KiB, "semaphore2" }, + [FSL_IMX8MM_SEMAPHORE1] = { 0x303b0000, 64 * KiB, "semaphore1" }, + [FSL_IMX8MM_GPC] = { 0x303a0000, 64 * KiB, "gpc" }, + [FSL_IMX8MM_SRC] = { 0x30390000, 64 * KiB, "src" }, + [FSL_IMX8MM_CCM] = { 0x30380000, 64 * KiB, "ccm" }, + [FSL_IMX8MM_SNVS_HP] = { 0x30370000, 64 * KiB, "snvs_hp" }, + [FSL_IMX8MM_ANA_PLL] = { 0x30360000, 64 * KiB, "ana_pll" }, + [FSL_IMX8MM_OCOTP_CTRL] = { 0x30350000, 64 * KiB, "ocotp_ctrl" }, + [FSL_IMX8MM_IOMUXC_GPR] = { 0x30340000, 64 * KiB, "iomuxc_gpr" }, + [FSL_IMX8MM_IOMUXC] = { 0x30330000, 64 * KiB, "iomuxc" }, + [FSL_IMX8MM_GPT3] = { 0x302f0000, 64 * KiB, "gpt3" }, + [FSL_IMX8MM_GPT2] = { 0x302e0000, 64 * KiB, "gpt2" }, + [FSL_IMX8MM_GPT1] = { 0x302d0000, 64 * KiB, "gpt1" }, + [FSL_IMX8MM_SDMA2] = { 0x302c0000, 64 * KiB, "sdma2" }, + [FSL_IMX8MM_SDMA3] = { 0x302b0000, 64 * KiB, "sdma3" }, + [FSL_IMX8MM_WDOG3] = { 0x302a0000, 64 * KiB, "wdog3" }, + [FSL_IMX8MM_WDOG2] = { 0x30290000, 64 * KiB, "wdog2" }, + [FSL_IMX8MM_WDOG1] = { 0x30280000, 64 * KiB, "wdog1" }, + [FSL_IMX8MM_ANA_OSC] = { 0x30270000, 64 * KiB, "ana_osc" }, + [FSL_IMX8MM_ANA_TSENSOR] = { 0x30260000, 64 * KiB, "ana_tsensor" }, + [FSL_IMX8MM_GPIO5] = { 0x30240000, 64 * KiB, "gpio5" }, + [FSL_IMX8MM_GPIO4] = { 0x30230000, 64 * KiB, "gpio4" }, + [FSL_IMX8MM_GPIO3] = { 0x30220000, 64 * KiB, "gpio3" }, + [FSL_IMX8MM_GPIO2] = { 0x30210000, 64 * KiB, "gpio2" }, + [FSL_IMX8MM_GPIO1] = { 0x30200000, 64 * KiB, "gpio1" }, + [FSL_IMX8MM_AIPS1_CONFIGURATION] = { 0x301f0000, 64 * KiB, "aips1_configuration" }, + [FSL_IMX8MM_SAI6] = { 0x30060000, 64 * KiB, "sai6" }, + [FSL_IMX8MM_SAI5] = { 0x30050000, 64 * KiB, "sai5" }, + [FSL_IMX8MM_SAI3] = { 0x30030000, 64 * KiB, "sai3" }, + [FSL_IMX8MM_SAI2] = { 0x30020000, 64 * KiB, "sai2" }, + [FSL_IMX8MM_SAI1] = { 0x30010000, 64 * KiB, "sai1" }, + + /* AIPS-1 End */ + + [FSL_IMX8MM_A53_DAP] = { 0x28000000, 16 * MiB, "a53_dap" }, + [FSL_IMX8MM_PCIE1_MEM] = { 0x18000000, 128 * MiB, "pcie1_mem" }, + [FSL_IMX8MM_QSPI_MEM] = { 0x08000000, 256 * MiB, "qspi_mem" }, + [FSL_IMX8MM_OCRAM] = { 0x00900000, 256 * KiB, "ocram" }, + [FSL_IMX8MM_TCM_DTCM] = { 0x00800000, 128 * KiB, "tcm_dtcm" }, + [FSL_IMX8MM_TCM_ITCM] = { 0x007e0000, 128 * KiB, "tcm_itcm" }, + [FSL_IMX8MM_OCRAM_S] = { 0x00180000, 32 * KiB, "ocram_s" }, + [FSL_IMX8MM_CAAM_MEM] = { 0x00100000, 32 * KiB, "caam_mem" }, + [FSL_IMX8MM_BOOT_ROM_PROTECTED] = { 0x0003f000, 4 * KiB, "boot_rom_protected" }, + [FSL_IMX8MM_BOOT_ROM] = { 0x00000000, 252 * KiB, "boot_rom" }, +}; + +static void fsl_imx8mm_init(Object *obj) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + FslImx8mmState *s = FSL_IMX8MM(obj); + const char *cpu_type = ms->cpu_type ?: ARM_CPU_TYPE_NAME("cortex-a53"); + int i; + + for (i = 0; i < MIN(ms->smp.cpus, FSL_IMX8MM_NUM_CPUS); i++) { + g_autofree char *name = g_strdup_printf("cpu%d", i); + object_initialize_child(obj, name, &s->cpu[i], cpu_type); + } + + object_initialize_child(obj, "gic", &s->gic, gicv3_class_name()); + + for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { + g_autofree char *name = g_strdup_printf("uart%d", i + 1); + object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); + } + +} + +static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + FslImx8mmState *s = FSL_IMX8MM(dev); + DeviceState *gicdev = DEVICE(&s->gic); + int i; + + if (ms->smp.cpus > FSL_IMX8MM_NUM_CPUS) { + error_setg(errp, "%s: Only %d CPUs are supported (%d requested)", + TYPE_FSL_IMX8MM, FSL_IMX8MM_NUM_CPUS, ms->smp.cpus); + return; + } + + /* CPUs */ + for (i = 0; i < ms->smp.cpus; i++) { + /* On uniprocessor, the CBAR is set to 0 */ + if (ms->smp.cpus > 1 && + object_property_find(OBJECT(&s->cpu[i]), "reset-cbar")) { + object_property_set_int(OBJECT(&s->cpu[i]), "reset-cbar", + fsl_imx8mm_memmap[FSL_IMX8MM_GIC_DIST].addr, + &error_abort); + } + + /* + * CNTFID0 base frequency in Hz of system counter + */ + object_property_set_int(OBJECT(&s->cpu[i]), "cntfrq", 8000000, + &error_abort); + + if (object_property_find(OBJECT(&s->cpu[i]), "has_el2")) { + object_property_set_bool(OBJECT(&s->cpu[i]), "has_el2", + !kvm_enabled(), &error_abort); + } + + if (object_property_find(OBJECT(&s->cpu[i]), "has_el3")) { + object_property_set_bool(OBJECT(&s->cpu[i]), "has_el3", + !kvm_enabled(), &error_abort); + } + + if (i) { + /* + * Secondary CPUs start in powered-down state (and can be + * powered up via the SRC system reset controller) + */ + object_property_set_bool(OBJECT(&s->cpu[i]), "start-powered-off", + true, &error_abort); + } + + if (!qdev_realize(DEVICE(&s->cpu[i]), NULL, errp)) { + return; + } + } + + /* GIC */ + { + SysBusDevice *gicsbd = SYS_BUS_DEVICE(&s->gic); + QList *redist_region_count; + bool pmu = object_property_get_bool(OBJECT(first_cpu), "pmu", NULL); + + qdev_prop_set_uint32(gicdev, "num-cpu", ms->smp.cpus); + qdev_prop_set_uint32(gicdev, "num-irq", + FSL_IMX8MM_NUM_IRQS + GIC_INTERNAL); + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, ms->smp.cpus); + qdev_prop_set_array(gicdev, "redist-region-count", redist_region_count); + object_property_set_link(OBJECT(&s->gic), "sysmem", + OBJECT(get_system_memory()), &error_fatal); + if (!sysbus_realize(gicsbd, errp)) { + return; + } + sysbus_mmio_map(gicsbd, 0, fsl_imx8mm_memmap[FSL_IMX8MM_GIC_DIST].addr); + sysbus_mmio_map(gicsbd, 1, fsl_imx8mm_memmap[FSL_IMX8MM_GIC_REDIST].addr); + + /* + * Wire the outputs from each CPU's generic timer and the GICv3 + * maintenance interrupt signal to the appropriate GIC PPI inputs, and + * the GIC's IRQ/FIQ interrupt outputs to the CPU's inputs. + */ + for (i = 0; i < ms->smp.cpus; i++) { + DeviceState *cpudev = DEVICE(&s->cpu[i]); + int intidbase = FSL_IMX8MM_NUM_IRQS + i * GIC_INTERNAL; + qemu_irq irq; + + /* + * Mapping from the output timer irq lines from the CPU to the + * GIC PPI inputs. + */ + static const int timer_irqs[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, + }; + + for (int j = 0; j < ARRAY_SIZE(timer_irqs); j++) { + irq = qdev_get_gpio_in(gicdev, intidbase + timer_irqs[j]); + qdev_connect_gpio_out(cpudev, j, irq); + } + + irq = qdev_get_gpio_in(gicdev, intidbase + ARCH_GIC_MAINT_IRQ); + qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", + 0, irq); + + irq = qdev_get_gpio_in(gicdev, intidbase + VIRTUAL_PMU_IRQ); + qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, irq); + + sysbus_connect_irq(gicsbd, i, + qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicsbd, i + ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + sysbus_connect_irq(gicsbd, i + 2 * ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicsbd, i + 3 * ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + + if (kvm_enabled()) { + if (pmu) { + assert(arm_feature(&s->cpu[i].env, ARM_FEATURE_PMU)); + if (kvm_irqchip_in_kernel()) { + kvm_arm_pmu_set_irq(&s->cpu[i], VIRTUAL_PMU_IRQ); + } + kvm_arm_pmu_init(&s->cpu[i]); + } + } + } + } + + /* UARTs */ + for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } serial_table[FSL_IMX8MM_NUM_UARTS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_UART1].addr, FSL_IMX8MM_UART1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_UART2].addr, FSL_IMX8MM_UART2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_UART3].addr, FSL_IMX8MM_UART3_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_UART4].addr, FSL_IMX8MM_UART4_IRQ }, + }; + + qdev_prop_set_chr(DEVICE(&s->uart[i]), "chardev", serial_hd(i)); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, serial_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, + qdev_get_gpio_in(gicdev, serial_table[i].irq)); + } + + /* On-Chip RAM */ + if (!memory_region_init_ram(&s->ocram, OBJECT(dev), "imx8mm.ocram", + fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].size, + errp)) { + return; + } + memory_region_add_subregion(get_system_memory(), + fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, + &s->ocram); + + /* Unimplemented devices */ + for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { + switch (i) { + case FSL_IMX8MM_GIC_DIST: + case FSL_IMX8MM_GIC_REDIST: + case FSL_IMX8MM_RAM: + case FSL_IMX8MM_OCRAM: + case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: + /* device implemented and treated above */ + break; + + default: + create_unimplemented_device(fsl_imx8mm_memmap[i].name, + fsl_imx8mm_memmap[i].addr, + fsl_imx8mm_memmap[i].size); + break; + } + } +} + +static void fsl_imx8mm_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = fsl_imx8mm_realize; + + dc->desc = "i.MX 8MM SoC"; +} + +static const TypeInfo fsl_imx8mm_types[] = { + { + .name = TYPE_FSL_IMX8MM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(FslImx8mmState), + .instance_init = fsl_imx8mm_init, + .class_init = fsl_imx8mm_class_init, + }, +}; + +DEFINE_TYPES(fsl_imx8mm_types) diff --git a/hw/arm/imx8mm-evk.c b/hw/arm/imx8mm-evk.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/arm/imx8mm-evk.c @@ -XXX,XX +XXX,XX @@ +/* + * NXP i.MX 8MM Evaluation Kit System Emulation + * + * Copyright (c) 2025, NXP Semiconductors + * Author: Gaurav Sharma <gaurav.sharma_7@nxp.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "system/address-spaces.h" +#include "hw/arm/boot.h" +#include "hw/arm/fsl-imx8mm.h" +#include "hw/arm/machines-qom.h" +#include "hw/core/boards.h" +#include "hw/core/qdev-properties.h" +#include "system/kvm.h" +#include "system/qtest.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include <libfdt.h> + +static void imx8mm_evk_modify_dtb(const struct arm_boot_info *info, void *fdt) +{ + int i, offset; + + /* Temporarily disable following nodes until they are implemented */ + const char *nodes_to_remove[] = { + "nxp,imx8mm-fspi", + "fsl,imx8mm-mipi-csi", + "fsl,imx8mm-mipi-dsim" + }; + + for (i = 0; i < ARRAY_SIZE(nodes_to_remove); i++) { + const char *dev_str = nodes_to_remove[i]; + + offset = fdt_node_offset_by_compatible(fdt, -1, dev_str); + while (offset >= 0) { + fdt_nop_node(fdt, offset); + offset = fdt_node_offset_by_compatible(fdt, offset, dev_str); + } + } + + /* Remove cpu-idle-states property from CPU nodes */ + offset = fdt_node_offset_by_compatible(fdt, -1, "arm,cortex-a53"); + while (offset >= 0) { + fdt_nop_property(fdt, offset, "cpu-idle-states"); + offset = fdt_node_offset_by_compatible(fdt, offset, "arm,cortex-a53"); + } + + if (kvm_enabled()) { + /* Use system counter frequency from host CPU to fix time in guest */ + offset = fdt_node_offset_by_compatible(fdt, -1, "arm,armv8-timer"); + while (offset >= 0) { + fdt_nop_property(fdt, offset, "clock-frequency"); + offset = fdt_node_offset_by_compatible(fdt, offset, "arm,armv8-timer"); + } + } +} + +static void imx8mm_evk_init(MachineState *machine) +{ + static struct arm_boot_info boot_info; + FslImx8mmState *s; + + if (machine->ram_size > FSL_IMX8MM_RAM_SIZE_MAX) { + error_report("RAM size " RAM_ADDR_FMT " above max supported (%08" PRIx64 ")", + machine->ram_size, FSL_IMX8MM_RAM_SIZE_MAX); + exit(1); + } + + boot_info = (struct arm_boot_info) { + .loader_start = FSL_IMX8MM_RAM_START, + .board_id = -1, + .ram_size = machine->ram_size, + .psci_conduit = QEMU_PSCI_CONDUIT_SMC, + .modify_dtb = imx8mm_evk_modify_dtb, + }; + + s = FSL_IMX8MM(object_new_with_props(TYPE_FSL_IMX8MM, OBJECT(machine), + "soc", &error_fatal, NULL)); + sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal); + + memory_region_add_subregion(get_system_memory(), FSL_IMX8MM_RAM_START, + machine->ram); + + if (!qtest_enabled()) { + arm_load_kernel(&s->cpu[0], machine, &boot_info); + } +} + +static const char *imx8mm_evk_get_default_cpu_type(const MachineState *ms) +{ + if (kvm_enabled()) { + return ARM_CPU_TYPE_NAME("host"); + } + + return ARM_CPU_TYPE_NAME("cortex-a53"); +} + +static void imx8mm_evk_machine_init(MachineClass *mc) +{ + mc->desc = "NXP i.MX 8MM EVK Board"; + mc->init = imx8mm_evk_init; + mc->max_cpus = FSL_IMX8MM_NUM_CPUS; + mc->default_cpus = FSL_IMX8MM_NUM_CPUS; + mc->default_ram_size = 2 * GiB; + mc->default_ram_id = "imx8mm-evk.ram"; + mc->get_default_cpu_type = imx8mm_evk_get_default_cpu_type; +} + +DEFINE_MACHINE_AARCH64("imx8mm-evk", imx8mm_evk_machine_init) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -XXX,XX +XXX,XX @@ arm_common_ss.add(when: 'CONFIG_ARMSSE', if_true: files('armsse.c')) arm_common_ss.add(when: 'CONFIG_FSL_IMX7', if_true: files('fsl-imx7.c', 'mcimx7d-sabre.c')) arm_common_ss.add(when: 'CONFIG_FSL_IMX8MP', if_true: files('fsl-imx8mp.c')) arm_common_ss.add(when: 'CONFIG_FSL_IMX8MP_EVK', if_true: files('imx8mp-evk.c')) +arm_common_ss.add(when: 'CONFIG_FSL_IMX8MM', if_true: files('fsl-imx8mm.c')) +arm_common_ss.add(when: 'CONFIG_FSL_IMX8MM_EVK', if_true: files('imx8mm-evk.c')) arm_common_ss.add(when: 'CONFIG_ARM_SMMUV3', if_true: files('smmuv3.c')) arm_common_ss.add(when: 'CONFIG_ARM_SMMUV3_ACCEL', if_true: files('smmuv3-accel.c')) stub_ss.add(files('smmuv3-accel-stubs.c')) diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ +/* + * i.MX 8MM SoC Definitions + * + * Copyright (c) 2025, NXP Semiconductors + * Author: Gaurav Sharma <gaurav.sharma_7@nxp.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef FSL_IMX8MM_H +#define FSL_IMX8MM_H + +#include "cpu.h" +#include "hw/char/imx_serial.h" +#include "hw/intc/arm_gicv3_common.h" +#include "qom/object.h" +#include "qemu/units.h" + +#define TYPE_FSL_IMX8MM "fsl-imx8mm" +OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mmState, FSL_IMX8MM) + +#define FSL_IMX8MM_RAM_START 0x40000000 +#define FSL_IMX8MM_RAM_SIZE_MAX (4 * GiB) + +enum FslImx8mmConfiguration { + FSL_IMX8MM_NUM_CPUS = 4, + FSL_IMX8MM_NUM_IRQS = 128, + FSL_IMX8MM_NUM_UARTS = 4, +}; + +struct FslImx8mmState { + SysBusDevice parent_obj; + + ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; + GICv3State gic; + IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; + MemoryRegion ocram; +}; + +enum FslImx8mmMemoryRegions { + FSL_IMX8MM_A53_DAP, + FSL_IMX8MM_AIPS1_CONFIGURATION, + FSL_IMX8MM_AIPS2_CONFIGURATION, + FSL_IMX8MM_AIPS3_CONFIGURATION, + FSL_IMX8MM_AIPS4_CONFIGURATION, + FSL_IMX8MM_ANA_OSC, + FSL_IMX8MM_ANA_PLL, + FSL_IMX8MM_ANA_TSENSOR, + FSL_IMX8MM_APBH_DMA, + FSL_IMX8MM_BOOT_ROM, + FSL_IMX8MM_BOOT_ROM_PROTECTED, + FSL_IMX8MM_CAAM, + FSL_IMX8MM_CAAM_MEM, + FSL_IMX8MM_CCM, + FSL_IMX8MM_CSU, + FSL_IMX8MM_DDR_CTL, + FSL_IMX8MM_DDR_PERF_MON, + FSL_IMX8MM_DDR_PHY, + FSL_IMX8MM_DDR_PHY_BROADCAST, + FSL_IMX8MM_ECSPI1, + FSL_IMX8MM_ECSPI2, + FSL_IMX8MM_ECSPI3, + FSL_IMX8MM_ENET1, + FSL_IMX8MM_GIC_DIST, + FSL_IMX8MM_GIC_REDIST, + FSL_IMX8MM_GPC, + FSL_IMX8MM_GPIO1, + FSL_IMX8MM_GPIO2, + FSL_IMX8MM_GPIO3, + FSL_IMX8MM_GPIO4, + FSL_IMX8MM_GPIO5, + FSL_IMX8MM_GPT1, + FSL_IMX8MM_GPT2, + FSL_IMX8MM_GPT3, + FSL_IMX8MM_GPT4, + FSL_IMX8MM_GPT5, + FSL_IMX8MM_GPT6, + FSL_IMX8MM_GPU2D, + FSL_IMX8MM_I2C1, + FSL_IMX8MM_I2C2, + FSL_IMX8MM_I2C3, + FSL_IMX8MM_I2C4, + FSL_IMX8MM_INTERCONNECT, + FSL_IMX8MM_IOMUXC, + FSL_IMX8MM_IOMUXC_GPR, + FSL_IMX8MM_MEDIA_BLK_CTL, + FSL_IMX8MM_LCDIF, + FSL_IMX8MM_MIPI_CSI, + FSL_IMX8MM_MIPI_DSI, + FSL_IMX8MM_MU_A, + FSL_IMX8MM_MU_B, + FSL_IMX8MM_OCOTP_CTRL, + FSL_IMX8MM_OCRAM, + FSL_IMX8MM_OCRAM_S, + FSL_IMX8MM_PCIE1, + FSL_IMX8MM_PCIE1_MEM, + FSL_IMX8MM_PCIE_PHY1, + FSL_IMX8MM_PERFMON1, + FSL_IMX8MM_PERFMON2, + FSL_IMX8MM_PWM1, + FSL_IMX8MM_PWM2, + FSL_IMX8MM_PWM3, + FSL_IMX8MM_PWM4, + FSL_IMX8MM_QOSC, + FSL_IMX8MM_QSPI, + FSL_IMX8MM_QSPI1_RX_BUFFER, + FSL_IMX8MM_QSPI1_TX_BUFFER, + FSL_IMX8MM_QSPI_MEM, + FSL_IMX8MM_RAM, + FSL_IMX8MM_RDC, + FSL_IMX8MM_SAI1, + FSL_IMX8MM_SAI2, + FSL_IMX8MM_SAI3, + FSL_IMX8MM_SAI5, + FSL_IMX8MM_SAI6, + FSL_IMX8MM_SDMA1, + FSL_IMX8MM_SDMA2, + FSL_IMX8MM_SDMA3, + FSL_IMX8MM_SEMAPHORE1, + FSL_IMX8MM_SEMAPHORE2, + FSL_IMX8MM_SEMAPHORE_HS, + FSL_IMX8MM_SNVS_HP, + FSL_IMX8MM_SPBA1, + FSL_IMX8MM_SRC, + FSL_IMX8MM_SYSCNT_CMP, + FSL_IMX8MM_SYSCNT_CTRL, + FSL_IMX8MM_SYSCNT_RD, + FSL_IMX8MM_TCM_DTCM, + FSL_IMX8MM_TCM_ITCM, + FSL_IMX8MM_TZASC, + FSL_IMX8MM_UART1, + FSL_IMX8MM_UART2, + FSL_IMX8MM_UART3, + FSL_IMX8MM_UART4, + FSL_IMX8MM_USB1, + FSL_IMX8MM_USB2, + FSL_IMX8MM_USB1_OTG, + FSL_IMX8MM_USB2_OTG, + FSL_IMX8MM_USDHC1, + FSL_IMX8MM_USDHC2, + FSL_IMX8MM_USDHC3, + FSL_IMX8MM_VPU, + FSL_IMX8MM_VPU_BLK_CTRL, + FSL_IMX8MM_VPU_G1_DECODER, + FSL_IMX8MM_VPU_G2_DECODER, + FSL_IMX8MM_WDOG1, + FSL_IMX8MM_WDOG2, + FSL_IMX8MM_WDOG3, +}; + +enum FslImx8mmIrqs { + FSL_IMX8MM_UART1_IRQ = 26, + FSL_IMX8MM_UART2_IRQ = 27, + FSL_IMX8MM_UART3_IRQ = 28, + FSL_IMX8MM_UART4_IRQ = 29, +}; + +#endif /* FSL_IMX8MM_H */ -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Add configurable properties for register reset values that differ between i.MX 8M variants (Plus, Mini, etc.). This allows the same device implementation to be shared across multiple SoCs. Properties added: - arm-pll-fdiv-ctl0-reset: ARM PLL divider control reset value Default value is set to match i.MX 8MP reset value (0x000FA031). This can be overridden in the variant like iMX8MM with its own reset value. Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/misc/imx8mp_analog.c | 12 +++++++++++- include/hw/misc/imx8mp_analog.h | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/hw/misc/imx8mp_analog.c b/hw/misc/imx8mp_analog.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/imx8mp_analog.c +++ b/hw/misc/imx8mp_analog.c @@ -XXX,XX +XXX,XX @@ #include "qemu/log.h" #include "hw/misc/imx8mp_analog.h" +#include "hw/core/qdev-properties.h" #include "migration/vmstate.h" #define ANALOG_PLL_LOCK BIT(31) @@ -XXX,XX +XXX,XX @@ static void imx8mp_analog_reset(DeviceState *dev) s->analog[ANALOG_VPU_PLL_LOCKD_CTRL] = 0x0010003f; s->analog[ANALOG_VPU_PLL_MNIT_CTRL] = 0x00280081; s->analog[ANALOG_ARM_PLL_GEN_CTRL] = 0x00000810; - s->analog[ANALOG_ARM_PLL_FDIV_CTL0] = 0x000fa031; + + /* Use property value instead of hardcoded */ + s->analog[ANALOG_ARM_PLL_FDIV_CTL0] = s->arm_pll_fdiv_ctl0_reset; + s->analog[ANALOG_ARM_PLL_LOCKD_CTRL] = 0x0010003f; s->analog[ANALOG_ARM_PLL_MNIT_CTRL] = 0x00280081; s->analog[ANALOG_SYS_PLL1_GEN_CTRL] = 0x0aaaa810; @@ -XXX,XX +XXX,XX @@ static const VMStateDescription imx8mp_analog_vmstate = { }, }; +static const Property imx8mp_analog_properties[] = { + DEFINE_PROP_UINT32("arm-pll-fdiv-ctl0-reset", IMX8MPAnalogState, + arm_pll_fdiv_ctl0_reset, 0x000fa031), /* imx8mp default */ +}; + static void imx8mp_analog_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, imx8mp_analog_reset); + device_class_set_props(dc, imx8mp_analog_properties); dc->vmsd = &imx8mp_analog_vmstate; dc->desc = "i.MX 8M Plus Analog Module"; } diff --git a/include/hw/misc/imx8mp_analog.h b/include/hw/misc/imx8mp_analog.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/imx8mp_analog.h +++ b/include/hw/misc/imx8mp_analog.h @@ -XXX,XX +XXX,XX @@ struct IMX8MPAnalogState { } mmio; uint32_t analog[ANALOG_MAX]; + + /* Property for variant-specific reset values */ + uint32_t arm_pll_fdiv_ctl0_reset; }; #endif /* IMX8MP_ANALOG_H */ -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Add the Analog IP to i.MX8MM SoC. iMX8MM and i.MX8MP uses the same Analog IP so the analog ip source will be shared. The ARM PLL divider control register (arm-pll-fdiv-ctl0) has a different reset value on i.MX8MM (0x000fa030) compared to i.MX8MP (0x000fa031). So iMX8MM will be overriding this property with its own reset-value. Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 12 ++++++++++++ include/hw/arm/fsl-imx8mm.h | 2 ++ 3 files changed, 15 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MP_EVK config FSL_IMX8MM bool select ARM_GIC + select FSL_IMX8MP_ANALOG select IMX config FSL_IMX8MM_EVK diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, "gic", &s->gic, gicv3_class_name()); + object_initialize_child(obj, "analog", &s->analog, TYPE_IMX8MP_ANALOG); + for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { g_autofree char *name = g_strdup_printf("uart%d", i + 1); object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) } } + /* Analog */ + object_property_set_uint(OBJECT(&s->analog), "arm-pll-fdiv-ctl0-reset", + 0x000fa030, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->analog), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->analog), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_ANA_PLL].addr); + /* UARTs */ for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { switch (i) { + case FSL_IMX8MM_ANA_PLL: case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_RAM: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/imx8mp_analog.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; GICv3State gic; + IMX8MPAnalogState analog; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; }; -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Add the Clock Control Module (CCM) device to i.MX8MM SoC. The CCM implementation is shared with i.MX8MP as the register layout is identical between the two variants.Hence iMX8MM will be using the source of iMX8MP CCM. Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 10 ++++++++++ include/hw/arm/fsl-imx8mm.h | 2 ++ 3 files changed, 13 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM bool select ARM_GIC select FSL_IMX8MP_ANALOG + select FSL_IMX8MP_CCM select IMX config FSL_IMX8MM_EVK diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, "gic", &s->gic, gicv3_class_name()); + object_initialize_child(obj, "ccm", &s->ccm, TYPE_IMX8MP_CCM); + object_initialize_child(obj, "analog", &s->analog, TYPE_IMX8MP_ANALOG); for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) } } + /* CCM */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->ccm), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ccm), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_CCM].addr); + /* Analog */ object_property_set_uint(OBJECT(&s->analog), "arm-pll-fdiv-ctl0-reset", 0x000fa030, &error_abort); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { switch (i) { case FSL_IMX8MM_ANA_PLL: + case FSL_IMX8MM_CCM: case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_RAM: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/char/imx_serial.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/misc/imx8mp_analog.h" +#include "hw/misc/imx8mp_ccm.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; GICv3State gic; + IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> SNVS contains an RTC which allows Linux to deal correctly with time Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/fsl-imx8mm.c | 10 ++++++++++ include/hw/arm/fsl-imx8mm.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, "analog", &s->analog, TYPE_IMX8MP_ANALOG); + object_initialize_child(obj, "snvs", &s->snvs, TYPE_IMX7_SNVS); + for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { g_autofree char *name = g_strdup_printf("uart%d", i + 1); object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* SNVS */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_SNVS_HP].addr); + /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { switch (i) { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_RAM: case FSL_IMX8MM_OCRAM: + case FSL_IMX8MM_SNVS_HP: case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: /* device implemented and treated above */ break; diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" #include "qom/object.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { GICv3State gic; IMX8MPCCMState ccm; IMX8MPAnalogState analog; + IMX7SNVSState snvs; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; }; -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of SD/MMC cards through a virtual SDHCI interface The emulated SDHCI controller allows guest OS to use emulated storage as a standard block device. This will allow running the images such as those generated by Buildroot. Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 25 +++++++++++++++++++++++++ hw/arm/imx8mm-evk.c | 17 +++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 7 +++++++ 4 files changed, 50 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select SDHCI config FSL_IMX8MM_EVK bool diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { + g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); + object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); + } } static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* USDHCs */ + for (i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } usdhc_table[FSL_IMX8MM_NUM_USDHCS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_USDHC1].addr, FSL_IMX8MM_USDHC1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_USDHC2].addr, FSL_IMX8MM_USDHC2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_USDHC3].addr, FSL_IMX8MM_USDHC3_IRQ }, + }; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->usdhc[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->usdhc[i]), 0, usdhc_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->usdhc[i]), 0, + qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); + } + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_OCRAM: case FSL_IMX8MM_SNVS_HP: case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: + case FSL_IMX8MM_USDHC1 ... FSL_IMX8MM_USDHC3: /* device implemented and treated above */ break; diff --git a/hw/arm/imx8mm-evk.c b/hw/arm/imx8mm-evk.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/imx8mm-evk.c +++ b/hw/arm/imx8mm-evk.c @@ -XXX,XX +XXX,XX @@ static void imx8mm_evk_init(MachineState *machine) memory_region_add_subregion(get_system_memory(), FSL_IMX8MM_RAM_START, machine->ram); + for (int i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { + BusState *bus; + DeviceState *carddev; + BlockBackend *blk; + DriveInfo *di = drive_get(IF_SD, i, 0); + + if (!di) { + continue; + } + + blk = blk_by_legacy_dinfo(di); + bus = qdev_get_child_bus(DEVICE(&s->usdhc[i]), "sd-bus"); + carddev = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(carddev, "drive", blk, &error_fatal); + qdev_realize_and_unref(carddev, bus, &error_fatal); + } + if (!qtest_enabled()) { arm_load_kernel(&s->cpu[0], machine, &boot_info); } diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/sd/sdhci.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, + FSL_IMX8MM_NUM_USDHCS = 3, }; struct FslImx8mmState { @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMX7SNVSState snvs; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; + SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; }; enum FslImx8mmMemoryRegions { @@ -XXX,XX +XXX,XX @@ enum FslImx8mmMemoryRegions { }; enum FslImx8mmIrqs { + FSL_IMX8MM_USDHC1_IRQ = 22, + FSL_IMX8MM_USDHC2_IRQ = 23, + FSL_IMX8MM_USDHC3_IRQ = 24, + FSL_IMX8MM_UART1_IRQ = 26, FSL_IMX8MM_UART2_IRQ = 27, FSL_IMX8MM_UART3_IRQ = 28, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> This enables support for Designware PCI Express Controller emulation It provides a controlled environment to debug the linux pci subsystem Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 3 +++ hw/arm/fsl-imx8mm.c | 30 ++++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 10 ++++++++++ 3 files changed, 43 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MP_EVK config FSL_IMX8MM bool + imply PCI_DEVICES select ARM_GIC select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX select SDHCI + select PCI_EXPRESS_DESIGNWARE + select PCI_EXPRESS_FSL_IMX8M_PHY config FSL_IMX8MM_EVK bool diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); + object_initialize_child(obj, "pcie_phy", &s->pcie_phy, + TYPE_FSL_IMX8M_PCIE_PHY); } static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, fsl_imx8mm_memmap[FSL_IMX8MM_SNVS_HP].addr); + /* PCIe */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_PCIE1].addr); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_INTA_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 1, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_INTB_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 2, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_INTC_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 3, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_INTD_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 4, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_MSI_IRQ)); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie_phy), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie_phy), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_PCIE_PHY1].addr); + /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { switch (i) { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_CCM: case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: + case FSL_IMX8MM_PCIE1: + case FSL_IMX8MM_PCIE_PHY1: case FSL_IMX8MM_RAM: case FSL_IMX8MM_OCRAM: case FSL_IMX8MM_SNVS_HP: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/pci-host/designware.h" +#include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; + DesignwarePCIEHost pcie; + FslImx8mPciePhyState pcie_phy; }; enum FslImx8mmMemoryRegions { @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_UART2_IRQ = 27, FSL_IMX8MM_UART3_IRQ = 28, FSL_IMX8MM_UART4_IRQ = 29, + + FSL_IMX8MM_PCI_INTA_IRQ = 122, + FSL_IMX8MM_PCI_INTB_IRQ = 123, + FSL_IMX8MM_PCI_INTC_IRQ = 124, + FSL_IMX8MM_PCI_INTD_IRQ = 125, + FSL_IMX8MM_PCI_MSI_IRQ = 127, }; #endif /* FSL_IMX8MM_H */ -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Enabled GPIO controller emulation Also updated the GPIO IRQ lines of iMX8MM Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/fsl-imx8mm.c | 54 +++++++++++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 14 ++++++++++ 2 files changed, 68 insertions(+) diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MM_NUM_GPIOS; i++) { + g_autofree char *name = g_strdup_printf("gpio%d", i + 1); + object_initialize_child(obj, name, &s->gpio[i], TYPE_IMX_GPIO); + } + for (i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* GPIOs */ + for (i = 0; i < FSL_IMX8MM_NUM_GPIOS; i++) { + static const struct { + hwaddr addr; + unsigned int irq_low; + unsigned int irq_high; + } gpio_table[FSL_IMX8MM_NUM_GPIOS] = { + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO1].addr, + FSL_IMX8MM_GPIO1_LOW_IRQ, + FSL_IMX8MM_GPIO1_HIGH_IRQ + }, + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO2].addr, + FSL_IMX8MM_GPIO2_LOW_IRQ, + FSL_IMX8MM_GPIO2_HIGH_IRQ + }, + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO3].addr, + FSL_IMX8MM_GPIO3_LOW_IRQ, + FSL_IMX8MM_GPIO3_HIGH_IRQ + }, + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO4].addr, + FSL_IMX8MM_GPIO4_LOW_IRQ, + FSL_IMX8MM_GPIO4_HIGH_IRQ + }, + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO5].addr, + FSL_IMX8MM_GPIO5_LOW_IRQ, + FSL_IMX8MM_GPIO5_HIGH_IRQ + }, + }; + object_property_set_bool(OBJECT(&s->gpio[i]), "has-edge-sel", true, + &error_abort); + object_property_set_bool(OBJECT(&s->gpio[i]), "has-upper-pin-irq", + true, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[i]), 0, gpio_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0, + qdev_get_gpio_in(gicdev, gpio_table[i].irq_low)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 1, + qdev_get_gpio_in(gicdev, gpio_table[i].irq_high)); + } + /* USDHCs */ for (i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_CCM: case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: + case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: case FSL_IMX8MM_PCIE1: case FSL_IMX8MM_PCIE_PHY1: case FSL_IMX8MM_RAM: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "hw/char/imx_serial.h" +#include "hw/gpio/imx_gpio.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mmState, FSL_IMX8MM) enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, + FSL_IMX8MM_NUM_GPIOS = 5, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, FSL_IMX8MM_NUM_USDHCS = 3, @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; GICv3State gic; + IMXGPIOState gpio[FSL_IMX8MM_NUM_GPIOS]; IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_UART3_IRQ = 28, FSL_IMX8MM_UART4_IRQ = 29, + FSL_IMX8MM_GPIO1_LOW_IRQ = 64, + FSL_IMX8MM_GPIO1_HIGH_IRQ = 65, + FSL_IMX8MM_GPIO2_LOW_IRQ = 66, + FSL_IMX8MM_GPIO2_HIGH_IRQ = 67, + FSL_IMX8MM_GPIO3_LOW_IRQ = 68, + FSL_IMX8MM_GPIO3_HIGH_IRQ = 69, + FSL_IMX8MM_GPIO4_LOW_IRQ = 70, + FSL_IMX8MM_GPIO4_HIGH_IRQ = 71, + FSL_IMX8MM_GPIO5_LOW_IRQ = 72, + FSL_IMX8MM_GPIO5_HIGH_IRQ = 73, + FSL_IMX8MM_PCI_INTA_IRQ = 122, FSL_IMX8MM_PCI_INTB_IRQ = 123, FSL_IMX8MM_PCI_INTC_IRQ = 124, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> This can be used to test and debug I2C device drivers. Added I2C interrupts Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 2 ++ hw/arm/fsl-imx8mm.c | 27 +++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 8 ++++++++ 3 files changed, 37 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MP_EVK config FSL_IMX8MM bool imply PCI_DEVICES + imply I2C_DEVICES select ARM_GIC select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select IMX_I2C select SDHCI select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MM_NUM_I2CS; i++) { + g_autofree char *name = g_strdup_printf("i2c%d", i + 1); + object_initialize_child(obj, name, &s->i2c[i], TYPE_IMX_I2C); + } + for (i = 0; i < FSL_IMX8MM_NUM_GPIOS; i++) { g_autofree char *name = g_strdup_printf("gpio%d", i + 1); object_initialize_child(obj, name, &s->gpio[i], TYPE_IMX_GPIO); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* I2Cs */ + for (i = 0; i < FSL_IMX8MM_NUM_I2CS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } i2c_table[FSL_IMX8MM_NUM_I2CS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_I2C1].addr, FSL_IMX8MM_I2C1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_I2C2].addr, FSL_IMX8MM_I2C2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_I2C3].addr, FSL_IMX8MM_I2C3_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_I2C4].addr, FSL_IMX8MM_I2C4_IRQ }, + }; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->i2c[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c[i]), 0, i2c_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[i]), 0, + qdev_get_gpio_in(gicdev, i2c_table[i].irq)); + } + /* GPIOs */ for (i = 0; i < FSL_IMX8MM_NUM_GPIOS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: + case FSL_IMX8MM_I2C1 ... FSL_IMX8MM_I2C4: case FSL_IMX8MM_PCIE1: case FSL_IMX8MM_PCIE_PHY1: case FSL_IMX8MM_RAM: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/gpio/imx_gpio.h" +#include "hw/i2c/imx_i2c.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mmState, FSL_IMX8MM) enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, FSL_IMX8MM_NUM_GPIOS = 5, + FSL_IMX8MM_NUM_I2CS = 4, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, FSL_IMX8MM_NUM_USDHCS = 3, @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; + IMXI2CState i2c[FSL_IMX8MM_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_UART3_IRQ = 28, FSL_IMX8MM_UART4_IRQ = 29, + FSL_IMX8MM_I2C1_IRQ = 35, + FSL_IMX8MM_I2C2_IRQ = 36, + FSL_IMX8MM_I2C3_IRQ = 37, + FSL_IMX8MM_I2C4_IRQ = 38, + FSL_IMX8MM_GPIO1_LOW_IRQ = 64, FSL_IMX8MM_GPIO1_HIGH_IRQ = 65, FSL_IMX8MM_GPIO2_LOW_IRQ = 66, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of ECSPI in iMX8MM Added SPI IRQ lines Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/fsl-imx8mm.c | 26 ++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + for (i = 0; i < FSL_IMX8MM_NUM_ECSPIS; i++) { + g_autofree char *name = g_strdup_printf("spi%d", i + 1); + object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); + } + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); } + /* ECSPIs */ + for (i = 0; i < FSL_IMX8MM_NUM_ECSPIS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } spi_table[FSL_IMX8MM_NUM_ECSPIS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_ECSPI1].addr, FSL_IMX8MM_ECSPI1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_ECSPI2].addr, FSL_IMX8MM_ECSPI2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_ECSPI3].addr, FSL_IMX8MM_ECSPI3_IRQ }, + }; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->spi[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi[i]), 0, spi_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi[i]), 0, + qdev_get_gpio_in(gicdev, spi_table[i].irq)); + } + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: + case FSL_IMX8MM_ECSPI1 ... FSL_IMX8MM_ECSPI3: case FSL_IMX8MM_I2C1 ... FSL_IMX8MM_I2C4: case FSL_IMX8MM_PCIE1: case FSL_IMX8MM_PCIE_PHY1: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" +#include "hw/ssi/imx_spi.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mmState, FSL_IMX8MM) enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, + FSL_IMX8MM_NUM_ECSPIS = 3, FSL_IMX8MM_NUM_GPIOS = 5, FSL_IMX8MM_NUM_I2CS = 4, FSL_IMX8MM_NUM_IRQS = 128, @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; + IMXSPIState spi[FSL_IMX8MM_NUM_ECSPIS]; IMXI2CState i2c[FSL_IMX8MM_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_UART3_IRQ = 28, FSL_IMX8MM_UART4_IRQ = 29, + FSL_IMX8MM_ECSPI1_IRQ = 31, + FSL_IMX8MM_ECSPI2_IRQ = 32, + FSL_IMX8MM_ECSPI3_IRQ = 33, + FSL_IMX8MM_I2C1_IRQ = 35, FSL_IMX8MM_I2C2_IRQ = 36, FSL_IMX8MM_I2C3_IRQ = 37, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of WDT in iMX8MM Added WDT IRQ lines Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 28 ++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 7 +++++++ 3 files changed, 36 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select SDHCI select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY + select WDT_IMX2 config FSL_IMX8MM_EVK bool diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); } + for (i = 0; i < FSL_IMX8MM_NUM_WDTS; i++) { + g_autofree char *name = g_strdup_printf("wdt%d", i); + object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT); + } + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, fsl_imx8mm_memmap[FSL_IMX8MM_SNVS_HP].addr); + /* Watchdogs */ + for (i = 0; i < FSL_IMX8MM_NUM_WDTS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } wdog_table[FSL_IMX8MM_NUM_WDTS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_WDOG1].addr, FSL_IMX8MM_WDOG1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_WDOG2].addr, FSL_IMX8MM_WDOG2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_WDOG3].addr, FSL_IMX8MM_WDOG3_IRQ }, + }; + + object_property_set_bool(OBJECT(&s->wdt[i]), "pretimeout-support", + true, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[i]), 0, wdog_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[i]), 0, + qdev_get_gpio_in(gicdev, wdog_table[i].irq)); + } + /* PCIe */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_SNVS_HP: case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: case FSL_IMX8MM_USDHC1 ... FSL_IMX8MM_USDHC3: + case FSL_IMX8MM_WDOG1 ... FSL_IMX8MM_WDOG3: /* device implemented and treated above */ break; diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" +#include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, FSL_IMX8MM_NUM_USDHCS = 3, + FSL_IMX8MM_NUM_WDTS = 3, }; struct FslImx8mmState { @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; + IMX2WdtState wdt[FSL_IMX8MM_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; }; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_GPIO5_LOW_IRQ = 72, FSL_IMX8MM_GPIO5_HIGH_IRQ = 73, + FSL_IMX8MM_WDOG1_IRQ = 78, + FSL_IMX8MM_WDOG2_IRQ = 79, + FSL_IMX8MM_WDOG3_IRQ = 10, + FSL_IMX8MM_PCI_INTA_IRQ = 122, FSL_IMX8MM_PCI_INTB_IRQ = 123, FSL_IMX8MM_PCI_INTC_IRQ = 124, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of GPT in iMX8MM Added GPT IRQ lines Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 54 +++++++++++++++++++++++++++++++++++++ hw/timer/imx_gpt.c | 26 ++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 11 ++++++++ include/hw/timer/imx_gpt.h | 2 ++ 5 files changed, 94 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select FSL_IMX8MP_CCM select IMX select IMX_I2C + select OR_IRQ select SDHCI select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MM_NUM_GPTS; i++) { + g_autofree char *name = g_strdup_printf("gpt%d", i + 1); + object_initialize_child(obj, name, &s->gpt[i], TYPE_IMX8MM_GPT); + } + object_initialize_child(obj, "gpt5-gpt6-irq", &s->gpt5_gpt6_irq, + TYPE_OR_IRQ); + for (i = 0; i < FSL_IMX8MM_NUM_I2CS; i++) { g_autofree char *name = g_strdup_printf("i2c%d", i + 1); object_initialize_child(obj, name, &s->i2c[i], TYPE_IMX_I2C); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* GPTs */ + object_property_set_int(OBJECT(&s->gpt5_gpt6_irq), "num-lines", 2, + &error_abort); + if (!qdev_realize(DEVICE(&s->gpt5_gpt6_irq), NULL, errp)) { + return; + } + + qdev_connect_gpio_out(DEVICE(&s->gpt5_gpt6_irq), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_GPT5_GPT6_IRQ)); + + for (i = 0; i < FSL_IMX8MM_NUM_GPTS; i++) { + hwaddr gpt_addrs[FSL_IMX8MM_NUM_GPTS] = { + fsl_imx8mm_memmap[FSL_IMX8MM_GPT1].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT2].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT3].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT4].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT5].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT6].addr, + }; + + s->gpt[i].ccm = IMX_CCM(&s->ccm); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpt[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpt[i]), 0, gpt_addrs[i]); + + if (i < FSL_IMX8MM_NUM_GPTS - 2) { + static const unsigned int gpt_irqs[FSL_IMX8MM_NUM_GPTS - 2] = { + FSL_IMX8MM_GPT1_IRQ, + FSL_IMX8MM_GPT2_IRQ, + FSL_IMX8MM_GPT3_IRQ, + FSL_IMX8MM_GPT4_IRQ, + }; + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt[i]), 0, + qdev_get_gpio_in(gicdev, gpt_irqs[i])); + } else { + int irq = i - FSL_IMX8MM_NUM_GPTS + 2; + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt[i]), 0, + qdev_get_gpio_in(DEVICE(&s->gpt5_gpt6_irq), irq)); + } + } + /* I2Cs */ for (i = 0; i < FSL_IMX8MM_NUM_I2CS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: + case FSL_IMX8MM_GPT1 ... FSL_IMX8MM_GPT6: case FSL_IMX8MM_ECSPI1 ... FSL_IMX8MM_ECSPI3: case FSL_IMX8MM_I2C1 ... FSL_IMX8MM_I2C4: case FSL_IMX8MM_PCIE1: diff --git a/hw/timer/imx_gpt.c b/hw/timer/imx_gpt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/timer/imx_gpt.c +++ b/hw/timer/imx_gpt.c @@ -XXX,XX +XXX,XX @@ * Originally written by Hans Jiang * Updated by Peter Chubb * Updated by Jean-Christophe Dubois <jcd@tribudubois.net> + * Updated by Gaurav Sharma <gaurav.sharma_7@nxp.com> * * This code is licensed under GPL version 2 or later. See * the COPYING file in the top-level directory. @@ -XXX,XX +XXX,XX @@ static const IMXClk imx8mp_gpt_clocks[] = { CLK_NONE, /* 111 not defined */ }; +static const IMXClk imx8mm_gpt_clocks[] = { + CLK_NONE, /* 000 No clock source */ + CLK_IPG, /* 001 ipg_clk, 532MHz */ + CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */ + CLK_EXT, /* 011 External clock */ + CLK_32k, /* 100 ipg_clk_32k */ + CLK_HIGH, /* 101 ipg_clk_16M */ + CLK_NONE, /* 110 not defined */ + CLK_NONE, /* 111 not defined */ +}; + /* Must be called from within ptimer_transaction_begin/commit block */ static void imx_gpt_set_freq(IMXGPTState *s) { @@ -XXX,XX +XXX,XX @@ static void imx8mp_gpt_init(Object *obj) s->clocks = imx8mp_gpt_clocks; } +static void imx8mm_gpt_init(Object *obj) +{ + IMXGPTState *s = IMX_GPT(obj); + + s->clocks = imx8mm_gpt_clocks; +} + static const TypeInfo imx25_gpt_info = { .name = TYPE_IMX25_GPT, .parent = TYPE_SYS_BUS_DEVICE, @@ -XXX,XX +XXX,XX @@ static const TypeInfo imx8mp_gpt_info = { .instance_init = imx8mp_gpt_init, }; +static const TypeInfo imx8mm_gpt_info = { + .name = TYPE_IMX8MM_GPT, + .parent = TYPE_IMX25_GPT, + .instance_init = imx8mm_gpt_init, +}; + static void imx_gpt_register_types(void) { type_register_static(&imx25_gpt_info); @@ -XXX,XX +XXX,XX @@ static void imx_gpt_register_types(void) type_register_static(&imx6ul_gpt_info); type_register_static(&imx7_gpt_info); type_register_static(&imx8mp_gpt_info); + type_register_static(&imx8mm_gpt_info); } type_init(imx_gpt_register_types) diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/or-irq.h" #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" +#include "hw/timer/imx_gpt.h" #include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, FSL_IMX8MM_NUM_ECSPIS = 3, FSL_IMX8MM_NUM_GPIOS = 5, + FSL_IMX8MM_NUM_GPTS = 6, FSL_IMX8MM_NUM_I2CS = 4, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; GICv3State gic; + IMXGPTState gpt[FSL_IMX8MM_NUM_GPTS]; IMXGPIOState gpio[FSL_IMX8MM_NUM_GPIOS]; IMX8MPCCMState ccm; IMX8MPAnalogState analog; @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMX2WdtState wdt[FSL_IMX8MM_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; + OrIRQState gpt5_gpt6_irq; }; enum FslImx8mmMemoryRegions { @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_I2C3_IRQ = 37, FSL_IMX8MM_I2C4_IRQ = 38, + FSL_IMX8MM_GPT1_IRQ = 55, + FSL_IMX8MM_GPT2_IRQ = 54, + FSL_IMX8MM_GPT3_IRQ = 53, + FSL_IMX8MM_GPT4_IRQ = 52, + FSL_IMX8MM_GPT5_GPT6_IRQ = 51, + FSL_IMX8MM_GPIO1_LOW_IRQ = 64, FSL_IMX8MM_GPIO1_HIGH_IRQ = 65, FSL_IMX8MM_GPIO2_LOW_IRQ = 66, diff --git a/include/hw/timer/imx_gpt.h b/include/hw/timer/imx_gpt.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/timer/imx_gpt.h +++ b/include/hw/timer/imx_gpt.h @@ -XXX,XX +XXX,XX @@ * Originally written by Hans Jiang * Updated by Peter Chubb * Updated by Jean-Christophe Dubois <jcd@tribudubois.net> + * Updated by Gaurav Sharma <gaurav.sharma_7@nxp.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -XXX,XX +XXX,XX @@ #define TYPE_IMX6UL_GPT "imx6ul.gpt" #define TYPE_IMX7_GPT "imx7.gpt" #define TYPE_IMX8MP_GPT "imx8mp.gpt" +#define TYPE_IMX8MM_GPT "imx8mm.gpt" #define TYPE_IMX_GPT TYPE_IMX25_GPT -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of ENET ethernet controller in iMX8MM Enables testing and debugging of network dependent drivers Added ENET MAC IRQ lines Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 24 ++++++++++++++++++++++++ hw/arm/imx8mm-evk.c | 1 + include/hw/arm/fsl-imx8mm.h | 10 +++++++++- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select IMX_FEC select IMX_I2C select OR_IRQ select SDHCI diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT); } + object_initialize_child(obj, "eth0", &s->enet, TYPE_IMX_ENET); + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, spi_table[i].irq)); } + /* ENET1 */ + object_property_set_uint(OBJECT(&s->enet), "phy-num", s->phy_num, + &error_abort); + object_property_set_uint(OBJECT(&s->enet), "tx-ring-num", 3, &error_abort); + qemu_configure_nic_device(DEVICE(&s->enet), true, NULL); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->enet), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->enet), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_ENET1].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->enet), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_ENET1_MAC_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->enet), 1, + qdev_get_gpio_in(gicdev, FSL_IMX6_ENET1_MAC_1588_IRQ)); + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: case FSL_IMX8MM_GPT1 ... FSL_IMX8MM_GPT6: case FSL_IMX8MM_ECSPI1 ... FSL_IMX8MM_ECSPI3: + case FSL_IMX8MM_ENET1: case FSL_IMX8MM_I2C1 ... FSL_IMX8MM_I2C4: case FSL_IMX8MM_PCIE1: case FSL_IMX8MM_PCIE_PHY1: @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) } } +static const Property fsl_imx8mm_properties[] = { + DEFINE_PROP_UINT32("fec1-phy-num", FslImx8mmState, phy_num, 0), + DEFINE_PROP_BOOL("fec1-phy-connected", FslImx8mmState, phy_connected, true), +}; + static void fsl_imx8mm_class_init(ObjectClass *oc, const void *data) { DeviceClass *dc = DEVICE_CLASS(oc); + device_class_set_props(dc, fsl_imx8mm_properties); dc->realize = fsl_imx8mm_realize; dc->desc = "i.MX 8MM SoC"; diff --git a/hw/arm/imx8mm-evk.c b/hw/arm/imx8mm-evk.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/imx8mm-evk.c +++ b/hw/arm/imx8mm-evk.c @@ -XXX,XX +XXX,XX @@ static void imx8mm_evk_init(MachineState *machine) s = FSL_IMX8MM(object_new_with_props(TYPE_FSL_IMX8MM, OBJECT(machine), "soc", &error_fatal, NULL)); + object_property_set_uint(OBJECT(s), "fec1-phy-num", 1, &error_fatal); sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal); memory_region_add_subregion(get_system_memory(), FSL_IMX8MM_RAM_START, diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" -#include "hw/or-irq.h" +#include "hw/net/imx_fec.h" +#include "hw/core/or-irq.h" #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMXI2CState i2c[FSL_IMX8MM_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; + IMXFECState enet; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; IMX2WdtState wdt[FSL_IMX8MM_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; OrIRQState gpt5_gpt6_irq; + + uint32_t phy_num; + bool phy_connected; }; enum FslImx8mmMemoryRegions { @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_WDOG2_IRQ = 79, FSL_IMX8MM_WDOG3_IRQ = 10, + FSL_IMX8MM_ENET1_MAC_IRQ = 118, + FSL_IMX6_ENET1_MAC_1588_IRQ = 121, + FSL_IMX8MM_PCI_INTA_IRQ = 122, FSL_IMX8MM_PCI_INTB_IRQ = 123, FSL_IMX8MM_PCI_INTC_IRQ = 124, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of USB on iMX8MM Enables testing and debugging of USB drivers Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 27 +++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 6 ++++++ 3 files changed, 34 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select SDHCI select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY + select USB_DWC3 select WDT_IMX2 config FSL_IMX8MM_EVK diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + for (i = 0; i < FSL_IMX8MM_NUM_USBS; i++) { + g_autofree char *name = g_strdup_printf("usb%d", i); + object_initialize_child(obj, name, &s->usb[i], TYPE_USB_DWC3); + } + for (i = 0; i < FSL_IMX8MM_NUM_ECSPIS; i++) { g_autofree char *name = g_strdup_printf("spi%d", i + 1); object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); } + /* USBs */ + for (i = 0; i < FSL_IMX8MM_NUM_USBS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } usb_table[FSL_IMX8MM_NUM_USBS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_USB1].addr, FSL_IMX8MM_USB1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_USB2].addr, FSL_IMX8MM_USB2_IRQ }, + }; + + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "p2", 1); + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "p3", 1); + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "slots", 2); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->usb[i]), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->usb[i]), 0, usb_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->usb[i].sysbus_xhci), 0, + qdev_get_gpio_in(gicdev, usb_table[i].irq)); + } + /* ECSPIs */ for (i = 0; i < FSL_IMX8MM_NUM_ECSPIS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_OCRAM: case FSL_IMX8MM_SNVS_HP: case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: + case FSL_IMX8MM_USB1 ... FSL_IMX8MM_USB2: case FSL_IMX8MM_USDHC1 ... FSL_IMX8MM_USDHC3: case FSL_IMX8MM_WDOG1 ... FSL_IMX8MM_WDOG3: /* device implemented and treated above */ diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" #include "hw/timer/imx_gpt.h" +#include "hw/usb/hcd-dwc3.h" #include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_I2CS = 4, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, + FSL_IMX8MM_NUM_USBS = 2, FSL_IMX8MM_NUM_USDHCS = 3, FSL_IMX8MM_NUM_WDTS = 3, }; @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMXFECState enet; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; IMX2WdtState wdt[FSL_IMX8MM_NUM_WDTS]; + USBDWC3 usb[FSL_IMX8MM_NUM_USBS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; OrIRQState gpt5_gpt6_irq; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_I2C3_IRQ = 37, FSL_IMX8MM_I2C4_IRQ = 38, + FSL_IMX8MM_USB1_IRQ = 40, + FSL_IMX8MM_USB2_IRQ = 41, + FSL_IMX8MM_GPT1_IRQ = 55, FSL_IMX8MM_GPT2_IRQ = 54, FSL_IMX8MM_GPT3_IRQ = 53, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Added script that would validate the iMX8MM emulation by checking the linux console log. If it succeeds, it will return:- ok 1 test_imx8mm_evk.Imx8mmEvkMachine.test_aarch64_imx8mm_evk_usdhc Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Reviewed-by: Thomas Huth <thuth@redhat.com> Reviewed-by: Bernhard Beschow <shentey@gmail.com> [PMM: Add missing blank lines that flake8 wants] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- MAINTAINERS | 1 + tests/functional/aarch64/meson.build | 2 + tests/functional/aarch64/test_imx8mm_evk.py | 69 +++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100755 tests/functional/aarch64/test_imx8mm_evk.py diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: hw/arm/fsl-imx8mm.c F: hw/arm/imx8mm-evk.c F: include/hw/arm/fsl-imx8mm.h F: docs/system/arm/imx8m.rst +F: tests/functional/aarch64/test_imx8mm_evk.py MCIMX8MP-EVK / i.MX8MP M: Bernhard Beschow <shentey@gmail.com> diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -XXX,XX +XXX,XX @@ test_aarch64_timeouts = { 'aspeed_ast2700a2' : 600, 'aspeed_ast2700fc' : 600, 'device_passthrough' : 720, + 'imx8mm_evk' : 240, 'imx8mp_evk' : 240, 'raspi4' : 480, 'reverse_debug' : 180, @@ -XXX,XX +XXX,XX @@ tests_aarch64_system_thorough = [ 'aspeed_ast2700fc', 'device_passthrough', 'hotplug_pci', + 'imx8mm_evk', 'imx8mp_evk', 'kvm', 'multiprocess', diff --git a/tests/functional/aarch64/test_imx8mm_evk.py b/tests/functional/aarch64/test_imx8mm_evk.py new file mode 100755 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/functional/aarch64/test_imx8mm_evk.py @@ -XXX,XX +XXX,XX @@ +#!/usr/bin/env python3 +# +# Functional test that boots a Linux kernel and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import LinuxKernelTest, Asset + + +class Imx8mmEvkMachine(LinuxKernelTest): + + ASSET_IMAGE = Asset( + ('https://cloud.debian.org/images/cloud/bookworm/20231210-1590/' + 'debian-12-generic-arm64-20231210-1590.tar.xz'), + '7ebf1577b32d5af6204df74b54ca2e4675de9b5a9fa14f3ff70b88eeb7b3b359') + + KERNEL_OFFSET = 0x51000000 + KERNEL_SIZE = 32622528 + INITRD_OFFSET = 0x76000000 + INITRD_SIZE = 30987766 + DTB_OFFSET = 0x64DB5000 + DTB_SIZE = 36812 + + def extract(self, in_path, out_path, offset, size): + try: + with open(in_path, "rb") as source: + source.seek(offset) + data = source.read(size) + with open(out_path, "wb") as target: + target.write(data) + except (IOError, ValueError) as e: + self.log.error(f"Failed to extract {out_path}: {e}") + raise + + def setUp(self): + super().setUp() + + self.image_path = self.scratch_file("disk.raw") + self.kernel_path = self.scratch_file("linux") + self.initrd_path = self.scratch_file("initrd.zstd") + self.dtb_path = self.scratch_file("imx8mm-evk.dtb") + + self.archive_extract(self.ASSET_IMAGE) + self.extract(self.image_path, self.kernel_path, + self.KERNEL_OFFSET, self.KERNEL_SIZE) + self.extract(self.image_path, self.initrd_path, + self.INITRD_OFFSET, self.INITRD_SIZE) + self.extract(self.image_path, self.dtb_path, + self.DTB_OFFSET, self.DTB_SIZE) + + def test_aarch64_imx8mm_evk_usdhc(self): + self.require_accelerator("tcg") + self.set_machine('imx8mm-evk') + self.vm.set_console(console_index=1) + self.vm.add_args('-m', '2G', + '-smp', '4', + '-kernel', self.kernel_path, + '-initrd', self.initrd_path, + '-dtb', self.dtb_path, + '-append', 'root=/dev/mmcblk2p1', + '-drive', f'file={self.image_path},if=sd,bus=2,' + 'format=raw,id=mmcblk2,snapshot=on') + + self.vm.launch() + self.wait_for_console_pattern('Welcome to ') + + +if __name__ == '__main__': + LinuxKernelTest.main() -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> As mentioned by Richard in [*]: We don't actually need any specific endianness here, because every byte has the same value. So we could simply drop MO_TE. That would produce a store in host-endianness, which will be fractionally more efficient on some hosts. [*] https://lore.kernel.org/qemu-devel/d64d3576-9188-4e74-afdc-3492c9feb8e0@linaro.org/ Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260414005348.4767-2-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/helper-a64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/tcg/helper-a64.c b/target/arm/tcg/helper-a64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/helper-a64.c +++ b/target/arm/tcg/helper-a64.c @@ -XXX,XX +XXX,XX @@ static uint64_t set_step_tags(CPUARMState *env, uint64_t toaddr, * the page dirty and will use the fast path. */ uint64_t repldata = data * 0x0101010101010101ULL; - MemOpIdx oi16 = make_memop_idx(MO_TE | MO_128, memidx); + MemOpIdx oi16 = make_memop_idx(MO_128, memidx); cpu_st16_mmu(env, toaddr, int128_make128(repldata, repldata), oi16, ra); mte_mops_set_tags(env, toaddr, 16, *mtedesc); return 16; -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Extract the implicit MO_TE definition in order to replace it in the next commit. Mechanical change using: $ for n in UW UL UQ UO SW SL SQ; do \ sed -i -e "s/MO_TE$n/MO_TE | MO_$n/" \ $(git grep -l MO_TE$n target/arm); \ done Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-3-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/m_helper.c | 6 +-- target/arm/tcg/mve_helper.c | 79 ++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/m_helper.c +++ b/target/arm/tcg/m_helper.c @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest) /* Note that these stores can throw exceptions on MPU faults */ ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); cpu_stl_mmu(env, sp, nextinst, oi, GETPC()); cpu_stl_mmu(env, sp + 4, saved_psr, oi, GETPC()); @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_vlstm)(CPUARMState *env, uint32_t fptr) bool lspact = env->v7m.fpccr[s] & R_V7M_FPCCR_LSPACT_MASK; uintptr_t ra = GETPC(); ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); assert(env->v7m.secure); @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_vlldm)(CPUARMState *env, uint32_t fptr) ARMCPU *cpu = env_archcpu(env); uintptr_t ra = GETPC(); ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); /* fptr is the value of Rn, the frame pointer we load the FP regs from */ diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) } DO_VLDR(vldrb, MO_UB, 1, uint8_t, ldb, 1, uint8_t) -DO_VLDR(vldrh, MO_TEUW, 2, uint16_t, ldw, 2, uint16_t) -DO_VLDR(vldrw, MO_TEUL, 4, uint32_t, ldl, 4, uint32_t) +DO_VLDR(vldrh, MO_TE | MO_UW, 2, uint16_t, ldw, 2, uint16_t) +DO_VLDR(vldrw, MO_TE | MO_UL, 4, uint32_t, ldl, 4, uint32_t) DO_VSTR(vstrb, MO_UB, 1, stb, 1, uint8_t) -DO_VSTR(vstrh, MO_TEUW, 2, stw, 2, uint16_t) -DO_VSTR(vstrw, MO_TEUL, 4, stl, 4, uint32_t) +DO_VSTR(vstrh, MO_TE | MO_UW, 2, stw, 2, uint16_t) +DO_VSTR(vstrw, MO_TE | MO_UL, 4, stl, 4, uint32_t) DO_VLDR(vldrb_sh, MO_SB, 1, int8_t, ldb, 2, int16_t) DO_VLDR(vldrb_sw, MO_SB, 1, int8_t, ldb, 4, int32_t) DO_VLDR(vldrb_uh, MO_UB, 1, uint8_t, ldb, 2, uint16_t) DO_VLDR(vldrb_uw, MO_UB, 1, uint8_t, ldb, 4, uint32_t) -DO_VLDR(vldrh_sw, MO_TESW, 2, int16_t, ldw, 4, int32_t) -DO_VLDR(vldrh_uw, MO_TEUW, 2, uint16_t, ldw, 4, uint32_t) +DO_VLDR(vldrh_sw, MO_TE | MO_SW, 2, int16_t, ldw, 4, int32_t) +DO_VLDR(vldrh_uw, MO_TE | MO_UW, 2, uint16_t, ldw, 4, uint32_t) DO_VSTR(vstrb_h, MO_UB, 1, stb, 2, int16_t) DO_VSTR(vstrb_w, MO_UB, 1, stb, 4, int32_t) -DO_VSTR(vstrh_w, MO_TEUW, 2, stw, 4, int32_t) +DO_VSTR(vstrh_w, MO_TE | MO_UW, 2, stw, 4, int32_t) #undef DO_VLDR #undef DO_VSTR @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_TEUW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (e = 0; e < 16 / 4; e++, mask >>= 4, eci_mask >>= 4) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_TEUW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (e = 0; e < 16 / 4; e++, mask >>= 4, eci_mask >>= 4) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_TEUW, 2, stw, 4, int32_t) DO_VLDR_SG(vldrb_sg_sh, MO_SB, int8_t, ldb, 2, int16_t, uint16_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_sw, MO_SB, int8_t, ldb, 4, int32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_sw, MO_TESW, int16_t, ldw, 4, int32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_sw, MO_TE | MO_SW, int16_t, ldw, 4, + int32_t, uint32_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_ub, MO_UB, uint8_t, ldb, 1, uint8_t, uint8_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_uh, MO_UB, uint8_t, ldb, 2, uint16_t, uint16_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_uw, MO_UB, uint8_t, ldb, 4, uint32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_uh, MO_TEUW, uint16_t, ldw, 2, uint16_t, uint16_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_uw, MO_TEUW, uint16_t, ldw, 4, uint32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrw_sg_uw, MO_TEUL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_uh, MO_TE | MO_UW, uint16_t, ldw, 2, + uint16_t, uint16_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_uw, MO_TE | MO_UW, uint16_t, ldw, 4, + uint32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrw_sg_uw, MO_TE | MO_UL, uint32_t, ldl, 4, + uint32_t, uint32_t, ADDR_ADD, false) DO_VLDR64_SG(vldrd_sg_ud, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_os_sw, MO_TESW, int16_t, ldw, 4, +DO_VLDR_SG(vldrh_sg_os_sw, MO_TE | MO_SW, int16_t, ldw, 4, int32_t, uint32_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrh_sg_os_uh, MO_TEUW, uint16_t, ldw, 2, +DO_VLDR_SG(vldrh_sg_os_uh, MO_TE | MO_UW, uint16_t, ldw, 2, uint16_t, uint16_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrh_sg_os_uw, MO_TEUW, uint16_t, ldw, 4, +DO_VLDR_SG(vldrh_sg_os_uw, MO_TE | MO_UW, uint16_t, ldw, 4, uint32_t, uint32_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrw_sg_os_uw, MO_TEUL, uint32_t, ldl, 4, +DO_VLDR_SG(vldrw_sg_os_uw, MO_TE | MO_UL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD_OSW, false) DO_VLDR64_SG(vldrd_sg_os_ud, ADDR_ADD_OSD, false) DO_VSTR_SG(vstrb_sg_ub, MO_UB, stb, 1, uint8_t, ADDR_ADD, false) DO_VSTR_SG(vstrb_sg_uh, MO_UB, stb, 2, uint16_t, ADDR_ADD, false) DO_VSTR_SG(vstrb_sg_uw, MO_UB, stb, 4, uint32_t, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_uh, MO_TEUW, stw, 2, uint16_t, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_uw, MO_TEUW, stw, 4, uint32_t, ADDR_ADD, false) -DO_VSTR_SG(vstrw_sg_uw, MO_TEUL, stl, 4, uint32_t, ADDR_ADD, false) +DO_VSTR_SG(vstrh_sg_uh, MO_TE | MO_UW, stw, 2, uint16_t, ADDR_ADD, false) +DO_VSTR_SG(vstrh_sg_uw, MO_TE | MO_UW, stw, 4, uint32_t, ADDR_ADD, false) +DO_VSTR_SG(vstrw_sg_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD, false) DO_VSTR64_SG(vstrd_sg_ud, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_os_uh, MO_TEUW, stw, 2, uint16_t, ADDR_ADD_OSH, false) -DO_VSTR_SG(vstrh_sg_os_uw, MO_TEUW, stw, 4, uint32_t, ADDR_ADD_OSH, false) -DO_VSTR_SG(vstrw_sg_os_uw, MO_TEUL, stl, 4, uint32_t, ADDR_ADD_OSW, false) +DO_VSTR_SG(vstrh_sg_os_uh, MO_TE | MO_UW, stw, 2, uint16_t, ADDR_ADD_OSH, false) +DO_VSTR_SG(vstrh_sg_os_uw, MO_TE | MO_UW, stw, 4, uint32_t, ADDR_ADD_OSH, false) +DO_VSTR_SG(vstrw_sg_os_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD_OSW, false) DO_VSTR64_SG(vstrd_sg_os_ud, ADDR_ADD_OSD, false) -DO_VLDR_SG(vldrw_sg_wb_uw, MO_TEUL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD, true) +DO_VLDR_SG(vldrw_sg_wb_uw, MO_TE | MO_UL, uint32_t, ldl, 4, + uint32_t, uint32_t, ADDR_ADD, true) DO_VLDR64_SG(vldrd_sg_wb_ud, ADDR_ADD, true) -DO_VSTR_SG(vstrw_sg_wb_uw, MO_TEUL, stl, 4, uint32_t, ADDR_ADD, true) +DO_VSTR_SG(vstrw_sg_wb_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD, true) DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) /* @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) static const uint8_t off[4] = { O1, O2, O3, O4 }; \ uint32_t addr, data; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) int y; /* y counts 0 2 0 2 */ \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0, y = 0; beat < 4; beat++, mask >>= 4, y ^= 2) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) uint32_t *qd; \ int y; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) uint32_t addr, data; \ uint8_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) int e; \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) uint32_t addr, data; \ uint32_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) static const uint8_t off[4] = { O1, O2, O3, O4 }; \ uint32_t addr, data; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) int y; /* y counts 0 2 0 2 */ \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0, y = 0; beat < 4; beat++, mask >>= 4, y ^= 2) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) uint32_t *qd; \ int y; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) uint32_t addr, data; \ uint8_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) int e; \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) uint32_t addr, data; \ uint32_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-4-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/mve_helper.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) uint16_t eci_mask = mve_eci_mask(env); \ unsigned b, e; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MFLAG | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + mmu_idx); \ /* \ * R_SXTM allows the dest reg to become UNKNOWN for abandoned \ * beats so we don't care if we update part of the dest and \ @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) uint16_t mask = mve_element_mask(env); \ unsigned b, e; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MFLAG | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + mmu_idx); \ for (b = 0, e = 0; b < 16; b += ESIZE, e++) { \ if (mask & (1 << b)) { \ cpu_##STTYPE##_mmu(env, addr, d[H##ESIZE(e)], oi, GETPC()); \ @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) } DO_VLDR(vldrb, MO_UB, 1, uint8_t, ldb, 1, uint8_t) -DO_VLDR(vldrh, MO_TE | MO_UW, 2, uint16_t, ldw, 2, uint16_t) -DO_VLDR(vldrw, MO_TE | MO_UL, 4, uint32_t, ldl, 4, uint32_t) +DO_VLDR(vldrh, MO_UW, 2, uint16_t, ldw, 2, uint16_t) +DO_VLDR(vldrw, MO_UL, 4, uint32_t, ldl, 4, uint32_t) DO_VSTR(vstrb, MO_UB, 1, stb, 1, uint8_t) -DO_VSTR(vstrh, MO_TE | MO_UW, 2, stw, 2, uint16_t) -DO_VSTR(vstrw, MO_TE | MO_UL, 4, stl, 4, uint32_t) +DO_VSTR(vstrh, MO_UW, 2, stw, 2, uint16_t) +DO_VSTR(vstrw, MO_UL, 4, stl, 4, uint32_t) DO_VLDR(vldrb_sh, MO_SB, 1, int8_t, ldb, 2, int16_t) DO_VLDR(vldrb_sw, MO_SB, 1, int8_t, ldb, 4, int32_t) DO_VLDR(vldrb_uh, MO_UB, 1, uint8_t, ldb, 2, uint16_t) DO_VLDR(vldrb_uw, MO_UB, 1, uint8_t, ldb, 4, uint32_t) -DO_VLDR(vldrh_sw, MO_TE | MO_SW, 2, int16_t, ldw, 4, int32_t) -DO_VLDR(vldrh_uw, MO_TE | MO_UW, 2, uint16_t, ldw, 4, uint32_t) +DO_VLDR(vldrh_sw, MO_SW, 2, int16_t, ldw, 4, int32_t) +DO_VLDR(vldrh_uw, MO_UW, 2, uint16_t, ldw, 4, uint32_t) DO_VSTR(vstrb_h, MO_UB, 1, stb, 2, int16_t) DO_VSTR(vstrb_w, MO_UB, 1, stb, 4, int32_t) -DO_VSTR(vstrh_w, MO_TE | MO_UW, 2, stw, 4, int32_t) +DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) #undef DO_VLDR #undef DO_VSTR -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-5-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/mve_helper.c | 43 +++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MFLAG | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + mmu_idx); \ for (e = 0; e < 16 / ESIZE; e++, mask >>= ESIZE, eci_mask >>= ESIZE) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MFLAG | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + mmu_idx); \ for (e = 0; e < 16 / ESIZE; e++, mask >>= ESIZE, eci_mask >>= ESIZE) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) DO_VLDR_SG(vldrb_sg_sh, MO_SB, int8_t, ldb, 2, int16_t, uint16_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_sw, MO_SB, int8_t, ldb, 4, int32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_sw, MO_TE | MO_SW, int16_t, ldw, 4, - int32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_sw, MO_SW, int16_t, ldw, 4, int32_t, uint32_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_ub, MO_UB, uint8_t, ldb, 1, uint8_t, uint8_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_uh, MO_UB, uint8_t, ldb, 2, uint16_t, uint16_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_uw, MO_UB, uint8_t, ldb, 4, uint32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_uh, MO_TE | MO_UW, uint16_t, ldw, 2, - uint16_t, uint16_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_uw, MO_TE | MO_UW, uint16_t, ldw, 4, - uint32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrw_sg_uw, MO_TE | MO_UL, uint32_t, ldl, 4, - uint32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_uh, MO_UW, uint16_t, ldw, 2, uint16_t, uint16_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_uw, MO_UW, uint16_t, ldw, 4, uint32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrw_sg_uw, MO_UL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD, false) DO_VLDR64_SG(vldrd_sg_ud, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_os_sw, MO_TE | MO_SW, int16_t, ldw, 4, +DO_VLDR_SG(vldrh_sg_os_sw, MO_SW, int16_t, ldw, 4, int32_t, uint32_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrh_sg_os_uh, MO_TE | MO_UW, uint16_t, ldw, 2, +DO_VLDR_SG(vldrh_sg_os_uh, MO_UW, uint16_t, ldw, 2, uint16_t, uint16_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrh_sg_os_uw, MO_TE | MO_UW, uint16_t, ldw, 4, +DO_VLDR_SG(vldrh_sg_os_uw, MO_UW, uint16_t, ldw, 4, uint32_t, uint32_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrw_sg_os_uw, MO_TE | MO_UL, uint32_t, ldl, 4, +DO_VLDR_SG(vldrw_sg_os_uw, MO_UL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD_OSW, false) DO_VLDR64_SG(vldrd_sg_os_ud, ADDR_ADD_OSD, false) DO_VSTR_SG(vstrb_sg_ub, MO_UB, stb, 1, uint8_t, ADDR_ADD, false) DO_VSTR_SG(vstrb_sg_uh, MO_UB, stb, 2, uint16_t, ADDR_ADD, false) DO_VSTR_SG(vstrb_sg_uw, MO_UB, stb, 4, uint32_t, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_uh, MO_TE | MO_UW, stw, 2, uint16_t, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_uw, MO_TE | MO_UW, stw, 4, uint32_t, ADDR_ADD, false) -DO_VSTR_SG(vstrw_sg_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD, false) +DO_VSTR_SG(vstrh_sg_uh, MO_UW, stw, 2, uint16_t, ADDR_ADD, false) +DO_VSTR_SG(vstrh_sg_uw, MO_UW, stw, 4, uint32_t, ADDR_ADD, false) +DO_VSTR_SG(vstrw_sg_uw, MO_UL, stl, 4, uint32_t, ADDR_ADD, false) DO_VSTR64_SG(vstrd_sg_ud, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_os_uh, MO_TE | MO_UW, stw, 2, uint16_t, ADDR_ADD_OSH, false) -DO_VSTR_SG(vstrh_sg_os_uw, MO_TE | MO_UW, stw, 4, uint32_t, ADDR_ADD_OSH, false) -DO_VSTR_SG(vstrw_sg_os_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD_OSW, false) +DO_VSTR_SG(vstrh_sg_os_uh, MO_UW, stw, 2, uint16_t, ADDR_ADD_OSH, false) +DO_VSTR_SG(vstrh_sg_os_uw, MO_UW, stw, 4, uint32_t, ADDR_ADD_OSH, false) +DO_VSTR_SG(vstrw_sg_os_uw, MO_UL, stl, 4, uint32_t, ADDR_ADD_OSW, false) DO_VSTR64_SG(vstrd_sg_os_ud, ADDR_ADD_OSD, false) -DO_VLDR_SG(vldrw_sg_wb_uw, MO_TE | MO_UL, uint32_t, ldl, 4, - uint32_t, uint32_t, ADDR_ADD, true) +DO_VLDR_SG(vldrw_sg_wb_uw, MO_UL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD, true) DO_VLDR64_SG(vldrd_sg_wb_ud, ADDR_ADD, true) -DO_VSTR_SG(vstrw_sg_wb_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD, true) +DO_VSTR_SG(vstrw_sg_wb_uw, MO_UL, stl, 4, uint32_t, ADDR_ADD, true) DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) /* -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Suggested-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-6-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/internals.h | 6 ++++++ target/arm/tcg/mve_helper.c | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/target/arm/internals.h b/target/arm/internals.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -XXX,XX +XXX,XX @@ #include "exec/hwaddr.h" #include "exec/vaddr.h" #include "exec/breakpoint.h" +#include "exec/memop.h" #include "accel/tcg/tb-cpu-state.h" #include "hw/core/registerfields.h" #include "tcg/tcg-gvec-desc.h" @@ -XXX,XX +XXX,XX @@ #define BANK_HYP 6 #define BANK_MON 7 +static inline MemOp mo_endian(CPUARMState *env) +{ + return EX_TBFLAG_ANY(env->hflags, BE_DATA) ? MO_BE : MO_LE; +} + static inline int arm_env_mmu_index(CPUARMState *env) { return EX_TBFLAG_ANY(env->hflags, MMUIDX); diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (e = 0; e < 16 / 4; e++, mask >>= 4, eci_mask >>= 4) { \ if (!(eci_mask & 1)) { \ continue; \ -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-7-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/mve_helper.c | 47 +++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) uint16_t eci_mask = mve_eci_mask(env); \ unsigned b, e; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MFLAG | MO_ALIGN, \ mmu_idx); \ /* \ * R_SXTM allows the dest reg to become UNKNOWN for abandoned \ @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) uint16_t mask = mve_element_mask(env); \ unsigned b, e; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MFLAG | MO_ALIGN, \ mmu_idx); \ for (b = 0, e = 0; b < 16; b += ESIZE, e++) { \ if (mask & (1 << b)) { \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MFLAG | MO_ALIGN, \ mmu_idx); \ for (e = 0; e < 16 / ESIZE; e++, mask >>= ESIZE, eci_mask >>= ESIZE) { \ if (!(eci_mask & 1)) { \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MFLAG | MO_ALIGN, \ mmu_idx); \ for (e = 0; e < 16 / ESIZE; e++, mask >>= ESIZE, eci_mask >>= ESIZE) { \ if (!(eci_mask & 1)) { \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (e = 0; e < 16 / 4; e++, mask >>= 4, eci_mask >>= 4) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) static const uint8_t off[4] = { O1, O2, O3, O4 }; \ uint32_t addr, data; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) int y; /* y counts 0 2 0 2 */ \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0, y = 0; beat < 4; beat++, mask >>= 4, y ^= 2) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) uint32_t *qd; \ int y; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) uint32_t addr, data; \ uint8_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) int e; \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) uint32_t addr, data; \ uint32_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) static const uint8_t off[4] = { O1, O2, O3, O4 }; \ uint32_t addr, data; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) int y; /* y counts 0 2 0 2 */ \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0, y = 0; beat < 4; beat++, mask >>= 4, y ^= 2) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) uint32_t *qd; \ int y; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) uint32_t addr, data; \ uint8_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) int e; \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) uint32_t addr, data; \ uint32_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-8-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/meson.build | 3 ++- target/arm/tcg/mve_helper.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/target/arm/tcg/meson.build b/target/arm/tcg/meson.build index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/meson.build +++ b/target/arm/tcg/meson.build @@ -XXX,XX +XXX,XX @@ arm_ss.add(when: 'TARGET_AARCH64', if_false: files('stubs32.c')) arm_ss.add(files( 'cpu32.c', 'm_helper.c', - 'mve_helper.c', )) arm_ss.add(when: 'TARGET_AARCH64', if_true: files( @@ -XXX,XX +XXX,XX @@ arm_common_system_ss.add( 'debug.c', 'hflags.c', 'gengvec.c', + 'mve_helper.c', 'neon_helper.c', 'op_helper.c', 'psci.c', @@ -XXX,XX +XXX,XX @@ arm_user_ss.add( 'debug.c', 'gengvec.c', 'hflags.c', + 'mve_helper.c', 'neon_helper.c', 'op_helper.c', 'tlb_helper.c', diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ #include "helper-mve.h" #include "internals.h" #include "vec_internal.h" -#include "accel/tcg/cpu-ldst.h" +#include "accel/tcg/cpu-ldst-common.h" #include "tcg/tcg.h" #include "fpu/softfloat.h" #include "crypto/clmul.h" -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-9-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/m_helper.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/m_helper.c +++ b/target/arm/tcg/m_helper.c @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest) /* Note that these stores can throw exceptions on MPU faults */ ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); cpu_stl_mmu(env, sp, nextinst, oi, GETPC()); cpu_stl_mmu(env, sp + 4, saved_psr, oi, GETPC()); @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_vlstm)(CPUARMState *env, uint32_t fptr) bool lspact = env->v7m.fpccr[s] & R_V7M_FPCCR_LSPACT_MASK; uintptr_t ra = GETPC(); ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); assert(env->v7m.secure); @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_vlldm)(CPUARMState *env, uint32_t fptr) ARMCPU *cpu = env_archcpu(env); uintptr_t ra = GETPC(); ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); /* fptr is the value of Rn, the frame pointer we load the FP regs from */ -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-10-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/m_helper.c | 2 +- target/arm/tcg/meson.build | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/m_helper.c +++ b/target/arm/tcg/m_helper.c @@ -XXX,XX +XXX,XX @@ #include "qemu/log.h" #include "exec/page-protection.h" #ifdef CONFIG_TCG -#include "accel/tcg/cpu-ldst.h" +#include "accel/tcg/cpu-ldst-common.h" #include "semihosting/common-semi.h" #endif #if !defined(CONFIG_USER_ONLY) diff --git a/target/arm/tcg/meson.build b/target/arm/tcg/meson.build index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/meson.build +++ b/target/arm/tcg/meson.build @@ -XXX,XX +XXX,XX @@ arm_ss.add(when: 'TARGET_AARCH64', if_false: files('stubs32.c')) arm_ss.add(files( 'cpu32.c', - 'm_helper.c', )) arm_ss.add(when: 'TARGET_AARCH64', if_true: files( @@ -XXX,XX +XXX,XX @@ arm_common_system_ss.add( 'debug.c', 'hflags.c', 'gengvec.c', + 'm_helper.c', 'mve_helper.c', 'neon_helper.c', 'op_helper.c', @@ -XXX,XX +XXX,XX @@ arm_user_ss.add( 'debug.c', 'gengvec.c', 'hflags.c', + 'm_helper.c', 'mve_helper.c', 'neon_helper.c', 'op_helper.c', -- 2.43.0
From: Thomas Huth <thuth@redhat.com> When writing to this register, QEMU currently aborts: $ echo "readl 0x10000018" | ./qemu-system-arm -audiodev none,id=snd0 \ -M integratorcp,accel=qtest,audiodev=snd0 -display none -qtest stdio [I 0.000000] OPENED [R +0.001907] readl 0x10000018 qemu: hardware error: integratorcm_read: CM_LMBUSCNT [...] Aborted (core dumped) This is bad, a guest should ideally never be able to kill QEMU like this. Now, according to the "Intergrator/CP User Guide" from: https://developer.arm.com/documentation/dui0159/b/porting-integrator-ap-and-im-pd1/registers "The Integrator/AP CM_LMBUSCNT has been removed." That means this register does not seem to be implemented on real CP boards at all, only for older AP boards. Thus it should be fine if we simply ignore this register in QEMU and handle it like all other unimplemented registers in the "default" handler of the case statement. Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3407 Signed-off-by: Thomas Huth <thuth@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260420064933.64765-1-thuth@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/integratorcp.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/hw/arm/integratorcp.c b/hw/arm/integratorcp.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/integratorcp.c +++ b/hw/arm/integratorcp.c @@ -XXX,XX +XXX,XX @@ static uint64_t integratorcm_read(void *opaque, hwaddr offset, } else { return s->cm_lock; } - case 6: /* CM_LMBUSCNT */ - /* ??? High frequency timer. */ - hw_error("integratorcm_read: CM_LMBUSCNT"); case 7: /* CM_AUXOSC */ return s->cm_auxosc; case 8: /* CM_SDRAM */ -- 2.43.0
include/hw/arm/primecell.h used to be more expansive, but now the only thing it defines is the ARM_SYSCTL_GPIO_* constants for the GPIO lines for the arm-sysctl system-control device used on the Realview, Versatile and Versatile Express boards. Replace it with a header file specific to that device. virt.c and vmapple.c included primecell.h despite not using the constants it defined; there we can simply drop the include entirely. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260416172627.690396-1-peter.maydell@linaro.org --- MAINTAINERS | 2 +- hw/arm/realview.c | 2 +- hw/arm/vexpress.c | 2 +- hw/arm/virt.c | 1 - hw/misc/arm_sysctl.c | 2 +- hw/vmapple/vmapple.c | 1 - include/hw/arm/primecell.h | 12 ------------ include/hw/misc/arm_sysctl.h | 16 ++++++++++++++++ 8 files changed, 20 insertions(+), 18 deletions(-) delete mode 100644 include/hw/arm/primecell.h create mode 100644 include/hw/misc/arm_sysctl.h diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: hw/ssi/pl022.c F: include/hw/ssi/pl022.h F: hw/rtc/pl031.c F: include/hw/rtc/pl031.h -F: include/hw/arm/primecell.h F: hw/timer/cmsdk-apb-timer.c F: include/hw/timer/cmsdk-apb-timer.h F: tests/qtest/cmsdk-apb-timer-test.c @@ -XXX,XX +XXX,XX @@ F: hw/*/versatile* F: hw/i2c/arm_sbcon_i2c.c F: include/hw/i2c/arm_sbcon_i2c.h F: hw/misc/arm_sysctl.c +F: include/hw/misc/arm_sysctl.h F: docs/system/arm/versatile.rst Virt diff --git a/hw/arm/realview.c b/hw/arm/realview.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/realview.c +++ b/hw/arm/realview.c @@ -XXX,XX +XXX,XX @@ #include "target/arm/cpu.h" #include "hw/core/sysbus.h" #include "hw/arm/boot.h" -#include "hw/arm/primecell.h" #include "hw/arm/machines-qom.h" #include "hw/core/split-irq.h" +#include "hw/misc/arm_sysctl.h" #include "hw/net/lan9118.h" #include "hw/net/smc91c111.h" #include "hw/pci/pci.h" diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/vexpress.c +++ b/hw/arm/vexpress.c @@ -XXX,XX +XXX,XX @@ #include "qemu/datadir.h" #include "hw/core/sysbus.h" #include "hw/arm/boot.h" -#include "hw/arm/primecell.h" #include "hw/arm/machines-qom.h" +#include "hw/misc/arm_sysctl.h" #include "hw/net/lan9118.h" #include "hw/i2c/i2c.h" #include "net/net.h" diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ #include "monitor/qdev.h" #include "hw/core/sysbus.h" #include "hw/arm/boot.h" -#include "hw/arm/primecell.h" #include "hw/arm/virt.h" #include "hw/arm/machines-qom.h" #include "hw/block/flash.h" diff --git a/hw/misc/arm_sysctl.c b/hw/misc/arm_sysctl.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/arm_sysctl.c +++ b/hw/misc/arm_sysctl.c @@ -XXX,XX +XXX,XX @@ #include "system/runstate.h" #include "qemu/bitops.h" #include "hw/core/sysbus.h" +#include "hw/misc/arm_sysctl.h" #include "migration/vmstate.h" -#include "hw/arm/primecell.h" #include "qemu/log.h" #include "qemu/module.h" #include "qom/object.h" diff --git a/hw/vmapple/vmapple.c b/hw/vmapple/vmapple.c index XXXXXXX..XXXXXXX 100644 --- a/hw/vmapple/vmapple.c +++ b/hw/vmapple/vmapple.c @@ -XXX,XX +XXX,XX @@ #include "hw/core/sysbus.h" #include "hw/usb/usb.h" #include "hw/arm/boot.h" -#include "hw/arm/primecell.h" #include "hw/char/pl011.h" #include "hw/intc/arm_gic.h" #include "hw/intc/arm_gicv3_common.h" diff --git a/include/hw/arm/primecell.h b/include/hw/arm/primecell.h deleted file mode 100644 index XXXXXXX..XXXXXXX --- a/include/hw/arm/primecell.h +++ /dev/null @@ -XXX,XX +XXX,XX @@ -#ifndef PRIMECELL_H -#define PRIMECELL_H - -/* Declarations for ARM PrimeCell based periperals. */ -/* Also includes some devices that are currently only used by the - ARM boards. */ - -/* arm_sysctl GPIO lines */ -#define ARM_SYSCTL_GPIO_MMC_WPROT 0 -#define ARM_SYSCTL_GPIO_MMC_CARDIN 1 - -#endif diff --git a/include/hw/misc/arm_sysctl.h b/include/hw/misc/arm_sysctl.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/misc/arm_sysctl.h @@ -XXX,XX +XXX,XX @@ +/* + * Status and system control registers for ARM RealView/Versatile boards. + * + * Copyright (c) Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_ARM_SYSCTL_H +#define HW_MISC_ARM_SYSCTL_H + +/* arm_sysctl inbound GPIO lines */ +#define ARM_SYSCTL_GPIO_MMC_WPROT 0 +#define ARM_SYSCTL_GPIO_MMC_CARDIN 1 + +#endif -- 2.43.0
If we create a normally-AArch64 CPU and configure it with aarch64=off, this will by default leave all the AArch64 ID register values in its ARMISARegisters struct untouched. That in turn means that tests of cpu_isar_feature(aa64_something, cpu) will return true. Until now we have had a design policy that you shouldn't check an aa64_ feature unless you know that the CPU has AArch64; but this is quite fragile as it's easy to forget and only causes a problem in the corner case where AArch64 was turned off. In particular, when we extend the ability to disable AArch64 from only KVM to also TCG there are many more aa64 feature check points which we would otherwise have to audit for whether they needed to be guarded with a check on ARM_FEATURE_AARCH64. Instead, make the CPU realize function zero out all the 64-bit ID registers if a TCG CPU doesn't have AArch64; this will make aa64_ feature tests generally return false. We only do this for TCG because only TCG really needs it, and for KVM it might be confusing to have QEMU's idea of the ID registers be different from KVM's. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260416165353.589569-2-peter.maydell@linaro.org --- target/arm/cpu.c | 35 +++++++++++++++++++++++++++++++++++ target/arm/cpu.h | 3 ++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ void arm_cpu_finalize_features(ARMCPU *cpu, Error **errp) } } +static void arm_clear_aarch64_idregs(ARMCPU *cpu) +{ + /* Zero out all the AArch64 ID registers in ARMISARegisters */ + SET_IDREG(&cpu->isar, ID_AA64ISAR0, 0); + SET_IDREG(&cpu->isar, ID_AA64ISAR1, 0); + SET_IDREG(&cpu->isar, ID_AA64ISAR2, 0); + SET_IDREG(&cpu->isar, ID_AA64PFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64PFR1, 0); + SET_IDREG(&cpu->isar, ID_AA64PFR2, 0); + SET_IDREG(&cpu->isar, ID_AA64MMFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64MMFR1, 0); + SET_IDREG(&cpu->isar, ID_AA64MMFR2, 0); + SET_IDREG(&cpu->isar, ID_AA64MMFR3, 0); + SET_IDREG(&cpu->isar, ID_AA64DFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64DFR1, 0); + SET_IDREG(&cpu->isar, ID_AA64AFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64AFR1, 0); + SET_IDREG(&cpu->isar, ID_AA64ZFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64SMFR0, 0); +} + static void arm_cpu_realizefn(DeviceState *dev, Error **errp) { CPUState *cs = CPU(dev); @@ -XXX,XX +XXX,XX @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) } #endif + /* + * A TCG aarch64=off CPU has no AArch64 at all, so we clear out the + * ID registers to avoid cpu_isar_feature(aa64_something, cpu) tests + * incorrectly returning true. We don't do this for other accelerators + * (which in practice means "for KVM", since no others have AArch32 + * guest support) because from KVM's point of view the AArch64 ID + * registers still exist and must have their correct values. So we + * avoid clearing them out so that we don't have QEMU and KVM with + * different ideas of the ID registers. + */ + if (tcg_enabled() && !arm_feature(env, ARM_FEATURE_AARCH64)) { + arm_clear_aarch64_idregs(cpu); + } + #ifdef CONFIG_USER_ONLY /* * User mode relies on IC IVAU instructions to catch modification of diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ struct ArchCPU { * Note that if you add an ID register to the ARMISARegisters struct * you need to also update the 32-bit and 64-bit versions of the * kvm_arm_get_host_cpu_features() function to correctly populate the - * field by reading the value from the KVM vCPU. + * field by reading the value from the KVM vCPU. If it is an AArch64 + * ID register then you also must update arm_clear_aarch64_idregs(). */ struct ARMISARegisters { uint32_t mvfr0; -- 2.43.0
Allow the 'aarch64=off' property, which is currently KVM-only, to be set for TCG CPUs also. Note that we don't permit it on the qemu-aarch64 user-mode binary: this makes no sense as that executable can only handle AArch64 syscalls (and it would also assert at startup since it doesn't compile in the A32-specific GDB xml files like arm-neon.xml). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Tested-by: Clément Chigot <chigot@adacore.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260416165353.589569-3-peter.maydell@linaro.org --- docs/system/arm/cpu-features.rst | 10 +++++---- target/arm/cpu-features.h | 5 +++++ target/arm/cpu.c | 36 ++++++++++++++++++++++++++++---- tests/qtest/arm-cpu-features.c | 8 ++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/cpu-features.rst +++ b/docs/system/arm/cpu-features.rst @@ -XXX,XX +XXX,XX @@ not implement ARMv8-A, will not have the ``aarch64`` CPU property. QEMU's support may be limited for some CPU features, only partially supporting the feature or only supporting the feature under certain configurations. For example, the ``aarch64`` CPU feature, which, when -disabled, enables the optional AArch32 CPU feature, is only supported -when using the KVM accelerator and when running on a host CPU type that -supports the feature. While ``aarch64`` currently only works with KVM, -it could work with TCG. CPU features that are specific to KVM are +disabled, enables the optional AArch32 CPU feature, can only be set to +``off`` on the TCG and KVM accelerators, and it cannot be set to +``off`` under KVM unless running on a host CPU type that supports +running guests in AArch32. + +CPU features that are inherently specific to KVM are prefixed with "kvm-" and are described in "KVM VCPU Features". CPU Feature Probing diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu-features.h +++ b/target/arm/cpu-features.h @@ -XXX,XX +XXX,XX @@ static inline bool isar_feature_aa64_aa32_el2(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64PFR0, EL2) >= 2; } +static inline bool isar_feature_aa64_aa32_el3(const ARMISARegisters *id) +{ + return FIELD_EX64_IDREG(id, ID_AA64PFR0, EL3) >= 2; +} + static inline bool isar_feature_aa64_ras(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64PFR0, RAS) != 0; diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ static void aarch64_cpu_set_aarch64(Object *obj, bool value, Error **errp) * uniform execution state like do_interrupt. */ if (value == false) { - if (!kvm_enabled() || !kvm_arm_aarch32_supported()) { - error_setg(errp, "'aarch64' feature cannot be disabled " - "unless KVM is enabled and 32-bit EL1 " - "is supported"); + if (kvm_enabled()) { + if (!kvm_arm_aarch32_supported()) { + error_setg(errp, "'aarch64' feature cannot be disabled for KVM " + "because this host does not support 32-bit EL1"); + return; + } + } else if (tcg_enabled()) { +#ifdef CONFIG_USER_ONLY + error_setg(errp, "'aarch64' feature cannot be disabled for " + "usermode emulator qemu-aarch64; use qemu-arm instead"); + return; +#else + bool aa32_at_highest_el; + if (arm_feature(&cpu->env, ARM_FEATURE_EL3)) { + aa32_at_highest_el = cpu_isar_feature(aa64_aa32_el3, cpu); + } else if (arm_feature(&cpu->env, ARM_FEATURE_EL2)) { + aa32_at_highest_el = cpu_isar_feature(aa64_aa32_el2, cpu); + } else { + aa32_at_highest_el = cpu_isar_feature(aa64_aa32_el1, cpu); + } + + if (!aa32_at_highest_el) { + error_setg(errp, "'aarch64' feature cannot be disabled for " + "this TCG CPU because it does not support 32-bit " + "execution at its highest implemented exception " + "level"); + return; + } +#endif + } else { + error_setg(errp, "'aarch64' feature cannot be disabled for " + "this accelerator"); return; } unset_feature(&cpu->env, ARM_FEATURE_AARCH64); diff --git a/tests/qtest/arm-cpu-features.c b/tests/qtest/arm-cpu-features.c index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/arm-cpu-features.c +++ b/tests/qtest/arm-cpu-features.c @@ -XXX,XX +XXX,XX @@ static void test_query_cpu_model_expansion(const void *data) sve_tests_default(qts, "max"); pauth_tests_default(qts, "max"); - /* Test that features that depend on KVM generate errors without. */ - assert_error(qts, "max", - "'aarch64' feature cannot be disabled " - "unless KVM is enabled and 32-bit EL1 " - "is supported", - "{ 'aarch64': false }"); + /* TCG allows us to turn off AArch64 on the 'max' CPU type */ + assert_set_feature(qts, "max", "aarch64", false); } qtest_quit(qts); -- 2.43.0
Add a basic test of the TCG 'aarch64=off' functionality; this is the same as our existing arm/test_virt test, but it runs the AArch32 guest kernel on qemu-system-aarch64 with -cpu max,aarch64=off. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Tested-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260416165353.589569-4-peter.maydell@linaro.org --- tests/functional/aarch64/meson.build | 1 + .../aarch64/test_virt_aarch64_off.py | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100755 tests/functional/aarch64/test_virt_aarch64_off.py diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -XXX,XX +XXX,XX @@ tests_aarch64_system_thorough = [ 'tcg_plugins', 'tuxrun', 'virt', + 'virt_aarch64_off', 'virt_gpu', 'virt_vbsa', 'xen', diff --git a/tests/functional/aarch64/test_virt_aarch64_off.py b/tests/functional/aarch64/test_virt_aarch64_off.py new file mode 100755 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/functional/aarch64/test_virt_aarch64_off.py @@ -XXX,XX +XXX,XX @@ +#!/usr/bin/env python3 +# +# Functional test that boots an AArch32 Linux kernel and checks the console +# on a TCG aarch64 CPU with aarch64=off. This is the same image etc +# as we use in tests/functional/arm/test_virt.py. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import LinuxKernelTest, Asset + +class ArmVirtMachine(LinuxKernelTest): + + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/' + 'releases/29/Everything/armhfp/os/images/pxeboot/vmlinuz'), + '18dd5f1a9a28bd539f9d047f7c0677211bae528e8712b40ca5a229a4ad8e2591') + + def test_arm_virt(self): + self.set_machine('virt') + # KVM aarch64=off requires a host CPU that supports it, so + # restrict the test to TCG only + self.require_accelerator('tcg') + self.vm.add_args('-cpu', 'max,aarch64=off') + + kernel_path = self.ASSET_KERNEL.fetch() + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + +if __name__ == '__main__': + LinuxKernelTest.main() -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> We introduce a datatype for a tolerance with respect to a given cpreg migration issue. The tolerance applies to a given cpreg kvm index, and can be of different types: a) mismatch in cpreg indexes - ToleranceNotOnBothEnds (cpreg index is allowed to be only present on one end) - ToleranceOnlySrcTestValue (cpreg index is allowed to be only present in source if its value @mask field matches @value) b) mismatch in cpreg values - ToleranceDiffInMask (value differences are allowed only within a mask) - ToleranceFieldLT (incoming field value must be less than a given value) - ToleranceFieldGT (incoming field value must be greater than a given value) A QLIST of such tolerances can be populated using a new helper: arm_register_cpreg_mig_tolerance() and arm_cpu_match_cpreg_mig_tolerance() allows to check whether a tolerance exists for a given kvm index and its criterion is matched. callers for those helpers will be introduced in subsequent patches. Only registration of migration tolerances related to cpreg index mismatch is currently allowed. Signed-off-by: Eric Auger <eric.auger@redhat.com> Message-id: 20260420140552.104369-2-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/cpu.c | 82 ++++++++++++++++++++++++++++++++++++++++++ target/arm/cpu.h | 1 + target/arm/internals.h | 54 ++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ void arm_register_el_change_hook(ARMCPU *cpu, ARMELChangeHookFn *hook, QLIST_INSERT_HEAD(&cpu->el_change_hooks, entry, node); } +static ARMCPRegMigTolerance *find_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx) +{ + ARMCPRegMigTolerance *t; + QLIST_FOREACH(t, &cpu->cpreg_mig_tolerances, node) { + if (t->kvmidx == kvmidx) { + return t; + } + } + return NULL; +} + +void arm_register_cpreg_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx, + uint64_t mask, uint64_t value, + ARMCPRegMigToleranceType type) +{ + ARMCPRegMigTolerance *entry; + + /* make sure the kvmidx has not tolerance already registered */ + assert(!find_mig_tolerance(cpu, kvmidx)); + + assert(type == ToleranceNotOnBothEnds || + type == ToleranceOnlySrcTestValue); + + entry = g_new0(ARMCPRegMigTolerance, 1); + + entry->kvmidx = kvmidx; + entry->mask = mask; + entry->value = value; + entry->type = type; + + QLIST_INSERT_HEAD(&cpu->cpreg_mig_tolerances, entry, node); +} + +bool arm_cpu_match_cpreg_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx, + uint64_t vmstate_value, uint64_t local_value, + ARMCPRegMigToleranceType type) +{ + ARMCPRegMigTolerance *t = find_mig_tolerance(cpu, kvmidx); + uint64_t diff, diff_outside_mask, field; + + if (!t || t->type != type) { + return false; + } + + if (type == ToleranceNotOnBothEnds) { + return true; + } + + if (type == ToleranceOnlySrcTestValue && + ((vmstate_value & t->mask) == t->value)) { + return true; + } + + /* Need to check the mask */ + diff = vmstate_value ^ local_value; + diff_outside_mask = diff & ~t->mask; + + if (diff_outside_mask) { + /* there are differences outside of the mask */ + return false; + } + if (type == ToleranceDiffInMask) { + /* differences only in the field, tolerance matched */ + return true; + } + /* need to compare field value against authorized ones */ + field = vmstate_value & t->mask; + if (type == ToleranceFieldLT && (field < t->value)) { + return true; + } + if (type == ToleranceFieldGT && (field > t->value)) { + return true; + } + return false; +} + static void cp_reg_reset(gpointer key, gpointer value, gpointer opaque) { /* Reset a single ARMCPRegInfo register */ @@ -XXX,XX +XXX,XX @@ static void arm_cpu_initfn(Object *obj) QLIST_INIT(&cpu->pre_el_change_hooks); QLIST_INIT(&cpu->el_change_hooks); + QLIST_INIT(&cpu->cpreg_mig_tolerances); #ifdef CONFIG_USER_ONLY # ifdef TARGET_AARCH64 @@ -XXX,XX +XXX,XX @@ static void arm_cpu_finalizefn(Object *obj) { ARMCPU *cpu = ARM_CPU(obj); ARMELChangeHook *hook, *next; + ARMCPRegMigTolerance *t, *n; g_hash_table_destroy(cpu->cp_regs); @@ -XXX,XX +XXX,XX @@ static void arm_cpu_finalizefn(Object *obj) QLIST_REMOVE(hook, node); g_free(hook); } + QLIST_FOREACH_SAFE(t, &cpu->cpreg_mig_tolerances, node, n) { + QLIST_REMOVE(t, node); + g_free(t); + } #ifndef CONFIG_USER_ONLY if (cpu->pmu_timer) { timer_free(cpu->pmu_timer); diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ struct ArchCPU { QLIST_HEAD(, ARMELChangeHook) pre_el_change_hooks; QLIST_HEAD(, ARMELChangeHook) el_change_hooks; + QLIST_HEAD(, ARMCPRegMigTolerance) cpreg_mig_tolerances; int32_t node_id; /* NUMA node this CPU belongs to */ diff --git a/target/arm/internals.h b/target/arm/internals.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -XXX,XX +XXX,XX @@ int compare_u64(const void *a, const void *b); /* Used in FEAT_MEC to set the MECIDWidthm1 field in the MECIDR_EL2 register. */ #define MECID_WIDTH 16 +typedef enum { + ToleranceNotOnBothEnds, + ToleranceOnlySrcTestValue, + ToleranceDiffInMask, + ToleranceFieldLT, + ToleranceFieldGT, +} ARMCPRegMigToleranceType; + +typedef struct ARMCPRegMigTolerance { + uint64_t kvmidx; + uint64_t mask; + uint64_t value; + ARMCPRegMigToleranceType type; + QLIST_ENTRY(ARMCPRegMigTolerance) node; +} ARMCPRegMigTolerance; + +/** + * arm_register_cpreg_mig_tolerance: + * Register a migration tolerance wrt one given cpreg identified by its + * @kvmidx. Calling this function twice for the same @kvmidx is a + * programming error and will cause an assertion failure. + * + * @cpu: vcpu to apply the migration tolerance on + * @kvmidx: kvm index of the cpreg the tolerance applies to + * @mask: bitmask where a difference is tolerated + * (relevant with ToleranceDiffInMask) + * @value: value the bitmask field is compared with + * (relevant with ToleranceFieldLT and ToleranceFieldGT) + * @type: type of the migration tolerance: + * - ToleranceNotOnBothEnds (cpreg index is allowed to be only present + * on one end) + * - ToleranceOnlySrcTestValue (cpreg index is allowed to be only + * present in source if its value @mask field matches @value) + * - ToleranceDiffInMask (mismatch in cpreg values are only tolerated + * if differences are within @mask) + * - ToleranceFieldLT (mismatch in cpreg values are only tolerated + * if incoming @bitmask field value is less than @value) + * - ToleranceFieldGT (mismatch in cpreg values are only tolerated + * if incoming @bitmask field value is greater than @value) + */ +void arm_register_cpreg_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx, + uint64_t mask, uint64_t value, + ARMCPRegMigToleranceType type); + +/** + * arm_cpu_match_cpreg_mig_tolerance: + * Check whether a tolerance of type @type exists for a given @kvmidx + * and the tolerance criterion is satisfied + */ +bool arm_cpu_match_cpreg_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx, + uint64_t vmstate_value, uint64_t local_value, + ARMCPRegMigToleranceType type); + + #endif -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> If there is a mismatch between the cpreg indexes found on both ends, check whether a tolerance was registered for the given kvmidx. If any, silence warning/errors. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-3-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/machine.c | 21 +++++++++++++++------ target/arm/trace-events | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/target/arm/machine.c b/target/arm/machine.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/machine.c +++ b/target/arm/machine.c @@ -XXX,XX +XXX,XX @@ static void handle_cpreg_missing_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx { g_autofree gchar *name = print_register_name(kvmidx); + if (arm_cpu_match_cpreg_mig_tolerance(cpu, kvmidx, + 0, 0, ToleranceNotOnBothEnds)) { + trace_tolerate_cpreg_missing_in_incoming_stream(name); + return; + } warn_report("%s: %s " "expected by the destination but not in the incoming stream: " "skip it", __func__, name); } /* - * Handle the situation where @kvmidx is in the incoming stream - * but not on destination. This currently fails the migration but - * we plan to accomodate some exceptions, hence the boolean returned value. + * Handle the situation where @kvmidx is in the incoming + * stream but not on destination. This fails the migration if + * no cpreg mig tolerance is matched for this @kvmidx + * Return true if the migration should eventually fail */ static bool handle_cpreg_only_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx) { g_autofree gchar *name = print_register_name(kvmidx); - bool fail = true; + if (arm_cpu_match_cpreg_mig_tolerance(cpu, kvmidx, + 0, 0, ToleranceNotOnBothEnds)) { + trace_tolerate_cpreg_only_in_incoming_stream(name); + return false; + } error_report("%s: %s in the incoming stream but unknown on the " "destination: fail migration", __func__, name); - - return fail; + return true; } static int cpu_post_load(void *opaque, int version_id) diff --git a/target/arm/trace-events b/target/arm/trace-events index XXXXXXX..XXXXXXX 100644 --- a/target/arm/trace-events +++ b/target/arm/trace-events @@ -XXX,XX +XXX,XX @@ arm_psci_call(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint32_t cpuid # machine.c cpu_post_load(uint32_t cpreg_vmstate_array_len, uint32_t cpreg_array_len) "cpreg_vmstate_array_len=%d cpreg_array_len=%d" +tolerate_cpreg_missing_in_incoming_stream(char *name) "%s is missing in incoming stream but this is explicitly tolerated" +tolerate_cpreg_only_in_incoming_stream(char *name) "%s is in incoming stream but not on destination but this is explicitly tolerated" -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> Pass the value of the incoming register to handle_cpreg_only_in_incoming_stream and check whether there is a matching ToleranceOnlySrcTestValue tolerance. Signed-off-by: Eric Auger <eric.auger@redhat.com> Message-id: 20260420140552.104369-4-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/machine.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/target/arm/machine.c b/target/arm/machine.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/machine.c +++ b/target/arm/machine.c @@ -XXX,XX +XXX,XX @@ static void handle_cpreg_missing_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx * no cpreg mig tolerance is matched for this @kvmidx * Return true if the migration should eventually fail */ -static bool handle_cpreg_only_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx) +static bool +handle_cpreg_only_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx, uint64_t value) { g_autofree gchar *name = print_register_name(kvmidx); if (arm_cpu_match_cpreg_mig_tolerance(cpu, kvmidx, - 0, 0, ToleranceNotOnBothEnds)) { + 0, 0, ToleranceNotOnBothEnds) || + arm_cpu_match_cpreg_mig_tolerance(cpu, kvmidx, + value, 0, ToleranceOnlySrcTestValue)) { trace_tolerate_cpreg_only_in_incoming_stream(name); return false; } @@ -XXX,XX +XXX,XX @@ static int cpu_post_load(void *opaque, int version_id) } if (cpu->cpreg_vmstate_indexes[v] < cpu->cpreg_indexes[i]) { fail = handle_cpreg_only_in_incoming_stream(cpu, - cpu->cpreg_vmstate_indexes[v++]); + cpu->cpreg_vmstate_indexes[v], + cpu->cpreg_vmstate_values[v]); + v++; continue; } /* matching register, copy the value over */ @@ -XXX,XX +XXX,XX @@ static int cpu_post_load(void *opaque, int version_id) */ for ( ; v < cpu->cpreg_vmstate_array_len; v++) { fail = handle_cpreg_only_in_incoming_stream(cpu, - cpu->cpreg_vmstate_indexes[v]); + cpu->cpreg_vmstate_indexes[v], + cpu->cpreg_vmstate_values[v]); } if (fail) { return -1; -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> Before linux v6.13 those registers were erroneously unconditionally exposed and this was fixed by commits: - 0fcb4eea5345 ("KVM: arm64: Hide TCR2_EL1 from userspace when disabled for guests") - a68cddbe47ef ("KVM: arm64: Hide S1PIE registers from userspace when disabled for guests") in v6.13. This means if we migrate from an old kernel host to a >= 6.13 kernel host, migration currently fails. Declare cpreg migration tolerance for those registers. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Sebastian Ott <sebott@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-5-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/cpu64.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -XXX,XX +XXX,XX @@ static void aarch64_a53_initfn(Object *obj) define_cortex_a72_a57_a53_cp_reginfo(cpu); } +#if defined(CONFIG_KVM) +static void kvm_arm_set_cpreg_mig_tolerances(ARMCPU *cpu) +{ + /* + * Registers that may be in the incoming stream and not exposed + * on the destination + */ + + /* + * TCR_EL1 was erroneously unconditionnally exposed before linux v6.13. + * See commit 0fcb4eea5345 ("KVM: arm64: Hide TCR2_EL1 from userspace + * when disabled for guests") + */ + arm_register_cpreg_mig_tolerance(cpu, ARM64_SYS_REG(3, 0, 2, 0, 3), + 0, 0, ToleranceNotOnBothEnds); + /* + * PIRE0_EL1 and PIR_EL1 were erroneously unconditionnally exposed + * before linux v6.13. See commit a68cddbe47ef ("KVM: arm64: Hide + * S1PIE registers from userspace when disabled for guests") + */ + arm_register_cpreg_mig_tolerance(cpu, ARM64_SYS_REG(3, 0, 10, 2, 2), + 0, 0, ToleranceNotOnBothEnds); + arm_register_cpreg_mig_tolerance(cpu, ARM64_SYS_REG(3, 0, 10, 2, 3), + 0, 0, ToleranceNotOnBothEnds); +} +#endif + static void aarch64_host_initfn(Object *obj) { ARMCPU *cpu = ARM_CPU(obj); @@ -XXX,XX +XXX,XX @@ static void aarch64_host_initfn(Object *obj) #endif #if defined(CONFIG_KVM) + kvm_arm_set_cpreg_mig_tolerances(cpu); kvm_arm_set_cpu_features_from_host(cpu); aarch64_add_sve_properties(obj); #elif defined(CONFIG_HVF) -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> KVM_REG_ARM_VENDOR_HYP_BMAP_2 pseudo FW register is exposed from v6.15 onwards. Backward migration from a >= v6.15 to an older kernel would fail without cpreg migration tolerance definition for this register. If the register is present on source but not on destination, its value must be checked to make sure it matches the reset value, ie. 0, meaning no service is exposed to the guest, hence the choice of a ToleranceOnlySrcTestValue migration tolerance. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-6-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/cpu64.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -XXX,XX +XXX,XX @@ static void kvm_arm_set_cpreg_mig_tolerances(ARMCPU *cpu) 0, 0, ToleranceNotOnBothEnds); arm_register_cpreg_mig_tolerance(cpu, ARM64_SYS_REG(3, 0, 10, 2, 3), 0, 0, ToleranceNotOnBothEnds); + + /* + * KVM_REG_ARM_VENDOR_HYP_BMAP_2 pseudo FW register is exposed + * from v6.15 onwards. Backward migration from a >= v6.15 to an older + * kernel would fail without cpreg migration tolerance definition. + * If the register is present on source but not on destination, make + * sure it has its reset value, ie. 0, meaning no service is exposed + * to the guest. + */ + arm_register_cpreg_mig_tolerance(cpu, KVM_REG_ARM_FW_FEAT_BMAP_REG(3), + UINT64_MAX, 0, ToleranceOnlySrcTestValue); } #endif -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> We want to remove AArch32 DBGDTRTX which was erroneously exposed. This was attempted by 655659a74a36b ("target/arm: Correct encoding of Debug Communications Channel registers") but it was discovered that the removal of this debug register broke forward migration on TCG. Now we have the cpreg migration tolerance infrastructure, we can declare one for the DBGDTRTX. This allow to revert the reinstate patch. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Sebastian Ott <sebott@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-7-eric.auger@redhat.com [PMM: revised comment, included note about when we can drop the workaround] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/helper.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/target/arm/helper.c b/target/arm/helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -XXX,XX +XXX,XX @@ void register_cp_regs_for_features(ARMCPU *cpu) .fgt = FGT_CLIDR_EL1, .resetvalue = GET_IDREG(isar, CLIDR) }; + uint64_t dbgtr_el0_kvmidx = + cpreg_to_kvm_id(ENCODE_CP_REG(14, 0, 1, 0, 5, 3, 0)); + define_one_arm_cp_reg(cpu, &clidr); define_arm_cp_regs(cpu, v7_cp_reginfo); define_debug_regs(cpu); + /* + * We used to incorrectly expose a non-existent AArch32 "DBGDTRTX" + * register with this encoding. This has been fixed by commit + * 655659a74a36 ("target/arm: Correct encoding of Debug + * Communications Channel registers") by the introduction of correct + * separate cpreg definitions for AA64 and AA32 versions. However, + * the old cpreg definition couldn't be removed without breaking + * migration, so commit 4f2b82f604 reinstated the bogus encoding + * for migration data only. + * + * Now that we have migration tolerance infrastructure, we can use + * this to allow forward migration from the buggy QEMU versions, + * accepting and ignoring the bogus register if it is in the + * source data. QEMU 11.0 was the last version that sent the + * bogus encoding, so this workaround can be removed at the point + * where we no longer care about migration from that version + * (i.e. when we remove the "virt-11.0" machine type). + */ + arm_register_cpreg_mig_tolerance(cpu, dbgtr_el0_kvmidx, + 0, 0, ToleranceNotOnBothEnds); } else { define_arm_cp_regs(cpu, not_v7_cp_reginfo); } -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> This reverts commit 4f2b82f60431 ("target/arm: Reinstate bogus AArch32 DBGDTRTX register for migration compat). We don't need that commit anymore as the AArch32 DBGDTRTX register is declared to be safe to ignore in the incoming migration stream. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Sebastian Ott <sebott@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-8-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/debug_helper.c | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/target/arm/debug_helper.c b/target/arm/debug_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/debug_helper.c +++ b/target/arm/debug_helper.c @@ -XXX,XX +XXX,XX @@ static void dbgclaimclr_write(CPUARMState *env, const ARMCPRegInfo *ri, env->cp15.dbgclaim &= ~(value & 0xFF); } -static CPAccessResult access_bogus(CPUARMState *env, const ARMCPRegInfo *ri, - bool isread) -{ - /* Always UNDEF, as if this cpreg didn't exist */ - return CP_ACCESS_UNDEFINED; -} - static const ARMCPRegInfo debug_cp_reginfo[] = { /* * DBGDRAR, DBGDSAR: always RAZ since we don't implement memory mapped @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo debug_cp_reginfo[] = { .opc0 = 2, .opc1 = 3, .crn = 0, .crm = 4, .opc2 = 0, .access = PL0_RW, .accessfn = access_tdcc, .type = ARM_CP_CONST, .resetvalue = 0 }, - /* - * This is not a real AArch32 register. We used to incorrectly expose - * this due to a QEMU bug; to avoid breaking migration compatibility we - * need to continue to provide it so that we don't fail the inbound - * migration when it tells us about a sysreg that we don't have. - * We set an always-fails .accessfn, which means that the guest doesn't - * actually see this register (it will always UNDEF, identically to if - * there were no cpreg definition for it other than that we won't print - * a LOG_UNIMP message about it), and we set the ARM_CP_NO_GDB flag so the - * gdbstub won't see it either. - * (We can't just set .access = 0, because add_cpreg_to_hashtable() - * helpfully ignores cpregs which aren't accessible to the highest - * implemented EL.) - * - * TODO: implement a system for being able to describe "this register - * can be ignored if it appears in the inbound stream"; then we can - * remove this temporary hack. - */ - { .name = "BOGUS_DBGDTR_EL0", .state = ARM_CP_STATE_AA32, - .cp = 14, .opc1 = 3, .crn = 0, .crm = 5, .opc2 = 0, - .access = PL0_RW, .accessfn = access_bogus, - .type = ARM_CP_CONST | ARM_CP_NO_GDB, .resetvalue = 0 }, /* * OSECCR_EL1 provides a mechanism for an operating system * to access the contents of EDECCR. EDECCR is not implemented though, -- 2.43.0
From: Osama Abdelkader <osama.abdelkader@gmail.com> fdt_node_offset_by_compatible(fdt, -1, compat) only finds the first match. If the blob has more than one node with the same compatible string, extra nodes will remain active. Remove all the matching nodes, using the same loop as imx8mp-evk.c does for this purpose. Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com> Message-id: 20260420162114.308519-1-osama.abdelkader@gmail.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/raspi4b.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/raspi4b.c +++ b/hw/arm/raspi4b.c @@ -XXX,XX +XXX,XX @@ static void raspi4_modify_dtb(const struct arm_boot_info *info, void *fdt) for (int i = 0; i < ARRAY_SIZE(nodes_to_remove); i++) { const char *dev_str = nodes_to_remove[i]; + int offset; - int offset = fdt_node_offset_by_compatible(fdt, -1, dev_str); - if (offset >= 0) { - if (!fdt_nop_node(fdt, offset)) { - warn_report("bcm2711 dtc: %s has been disabled!", dev_str); + offset = fdt_node_offset_by_compatible(fdt, -1, dev_str); + while (offset >= 0) { + if (fdt_nop_node(fdt, offset) == 0) { + warn_report("bcm2711 dtb: %s has been disabled!", dev_str); } + offset = fdt_node_offset_by_compatible(fdt, offset, dev_str); } } -- 2.43.0
From: Yucai Liu <1486344514@qq.com> Implement a basic i.MX6UL LCDIF controller model with MMIO registers, frame-done interrupt behavior, and framebuffer-backed display updates for RGB565 and XRGB8888 input formats. Place the LCDIF device under hw/display and build it via a dedicated CONFIG_IMX6UL_LCDIF symbol. Model register fields with registerfields.h helpers and provide migration support via vmstate. Signed-off-by: Yucai Liu <1486344514@qq.com> Message-id: 20260412110240.93116-2-yangyanglan718@gmail.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- MAINTAINERS | 2 + hw/display/Kconfig | 4 + hw/display/imx6ul_lcdif.c | 453 ++++++++++++++++++++++++++++++ hw/display/meson.build | 1 + include/hw/display/imx6ul_lcdif.h | 37 +++ 5 files changed, 497 insertions(+) create mode 100644 hw/display/imx6ul_lcdif.c create mode 100644 include/hw/display/imx6ul_lcdif.h diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ L: qemu-arm@nongnu.org S: Odd Fixes F: hw/arm/mcimx6ul-evk.c F: hw/arm/fsl-imx6ul.c +F: hw/display/imx6ul_lcdif.c F: hw/misc/imx6ul_ccm.c F: include/hw/arm/fsl-imx6ul.h +F: include/hw/display/imx6ul_lcdif.h F: include/hw/misc/imx6ul_ccm.h F: docs/system/arm/mcimx6ul-evk.rst diff --git a/hw/display/Kconfig b/hw/display/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/display/Kconfig +++ b/hw/display/Kconfig @@ -XXX,XX +XXX,XX @@ config PL110 bool select FRAMEBUFFER +config IMX6UL_LCDIF + bool + select FRAMEBUFFER + config SII9022 bool depends on I2C diff --git a/hw/display/imx6ul_lcdif.c b/hw/display/imx6ul_lcdif.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/display/imx6ul_lcdif.c @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * i.MX6UL LCDIF controller + * + * Copyright (c) 2026 Yucai Liu <1486344514@qq.com> + */ + +#include "qemu/osdep.h" +#include "hw/display/imx6ul_lcdif.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "hw/display/framebuffer.h" +#include "migration/vmstate.h" +#include "system/address-spaces.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "ui/pixel_ops.h" + +#define LCDIF_MMIO_SIZE (16 * KiB) +#define LCDIF_RESET_CTRL1 0x000f0000 + +REG32(CTRL, 0x00) + FIELD(CTRL, RUN, 0, 1) + FIELD(CTRL, WORD_LENGTH, 8, 2) +REG32(CTRL1, 0x10) + FIELD(CTRL1, CUR_FRAME_DONE_IRQ, 9, 1) + FIELD(CTRL1, CUR_FRAME_DONE_IRQ_EN, 13, 1) + FIELD(CTRL1, BYTE_PACKING_FORMAT, 16, 4) +REG32(V4_TRANSFER_COUNT, 0x30) + FIELD(V4_TRANSFER_COUNT, H_COUNT, 0, 16) + FIELD(V4_TRANSFER_COUNT, V_COUNT, 16, 16) +REG32(V4_CUR_BUF, 0x40) +REG32(V4_NEXT_BUF, 0x50) +REG32(AS_NEXT_BUF, 0x230) + +#define REG_SET 0x4 +#define REG_CLR 0x8 +#define REG_TOG 0xc + +#define CTRL_WORD_LENGTH_16 0 +#define CTRL_WORD_LENGTH_24 3 + +#define FRAME_PERIOD_NS (16 * 1000 * 1000ULL) + +enum IMX6ULLCDIFReg { + IMX6UL_LCDIF_REG_CTRL = A_CTRL >> 4, + IMX6UL_LCDIF_REG_CTRL1 = A_CTRL1 >> 4, + IMX6UL_LCDIF_REG_V4_TRANSFER_COUNT = A_V4_TRANSFER_COUNT >> 4, + IMX6UL_LCDIF_REG_V4_CUR_BUF = A_V4_CUR_BUF >> 4, + IMX6UL_LCDIF_REG_V4_NEXT_BUF = A_V4_NEXT_BUF >> 4, + IMX6UL_LCDIF_REG_AS_NEXT_BUF = A_AS_NEXT_BUF >> 4, +}; + +static inline bool imx6ul_lcdif_reg_exists(hwaddr reg) +{ + return (reg >> 4) < IMX6UL_LCDIF_REGS_NUM; +} + +static inline bool imx6ul_lcdif_reg_has_setclr(hwaddr reg) +{ + switch (reg) { + case A_CTRL: + case A_CTRL1: + return true; + default: + return false; + } +} + +static inline bool imx6ul_lcdif_is_running(IMX6ULLCDIFState *s) +{ + uint32_t ctrl = s->regs[IMX6UL_LCDIF_REG_CTRL]; + + return FIELD_EX32(ctrl, CTRL, RUN); +} + +static inline bool imx6ul_lcdif_frame_done_pending(IMX6ULLCDIFState *s) +{ + uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1]; + + return FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ); +} + +static void imx6ul_lcdif_schedule_frame(IMX6ULLCDIFState *s) +{ + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + timer_mod(s->frame_timer, now + FRAME_PERIOD_NS); +} + +static void imx6ul_lcdif_maybe_schedule_frame(IMX6ULLCDIFState *s) +{ + if (imx6ul_lcdif_is_running(s) && !imx6ul_lcdif_frame_done_pending(s)) { + imx6ul_lcdif_schedule_frame(s); + } else { + timer_del(s->frame_timer); + } +} + +static void imx6ul_lcdif_update_irq(IMX6ULLCDIFState *s) +{ + uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1]; + bool level = FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ_EN) && + FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ); + + qemu_set_irq(s->irq, level); +} + +static void imx6ul_lcdif_frame_done(IMX6ULLCDIFState *s) +{ + uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1]; + + ctrl1 = FIELD_DP32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ, 1); + s->regs[IMX6UL_LCDIF_REG_CTRL1] = ctrl1; + imx6ul_lcdif_update_irq(s); +} + +static void imx6ul_lcdif_draw_line_rgb565(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + int i; + + for (i = 0; i < width; i++) { + uint16_t pixel = lduw_le_p(src); + uint8_t r = ((pixel >> 11) & 0x1f) << 3; + uint8_t g = ((pixel >> 5) & 0x3f) << 2; + uint8_t b = (pixel & 0x1f) << 3; + + *dst32++ = rgb_to_pixel32(r, g, b); + src += 2; + } +} + +static void imx6ul_lcdif_draw_line_xrgb8888(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + int i; + + for (i = 0; i < width; i++) { + uint32_t pixel = ldl_le_p(src); + uint8_t r = (pixel >> 16) & 0xff; + uint8_t g = (pixel >> 8) & 0xff; + uint8_t b = pixel & 0xff; + + *dst32++ = rgb_to_pixel32(r, g, b); + src += 4; + } +} + +static void imx6ul_lcdif_update_display(void *opaque) +{ + IMX6ULLCDIFState *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + uint32_t transfer_count = s->regs[IMX6UL_LCDIF_REG_V4_TRANSFER_COUNT]; + uint32_t width = FIELD_EX32(transfer_count, V4_TRANSFER_COUNT, H_COUNT); + uint32_t height = FIELD_EX32(transfer_count, V4_TRANSFER_COUNT, V_COUNT); + uint32_t ctrl = s->regs[IMX6UL_LCDIF_REG_CTRL]; + uint32_t frame_base = s->regs[IMX6UL_LCDIF_REG_V4_CUR_BUF]; + drawfn fn; + int first = 0; + int last = 0; + int src_width; + + if (!imx6ul_lcdif_is_running(s) || width == 0 || height == 0) { + return; + } + + switch (FIELD_EX32(ctrl, CTRL, WORD_LENGTH)) { + case CTRL_WORD_LENGTH_16: + s->src_bpp = 2; + fn = imx6ul_lcdif_draw_line_rgb565; + break; + case CTRL_WORD_LENGTH_24: + s->src_bpp = 4; + fn = imx6ul_lcdif_draw_line_xrgb8888; + break; + default: + return; + } + + if (surface_width(surface) != width || surface_height(surface) != height) { + qemu_console_resize(s->con, width, height); + surface = qemu_console_surface(s->con); + s->invalidate = true; + } + + src_width = width * s->src_bpp; + if (s->invalidate || s->fb_base != frame_base || + s->src_width != src_width || s->rows != height) { + framebuffer_update_memory_section(&s->fbsection, get_system_memory(), + frame_base, height, src_width); + s->fb_base = frame_base; + s->src_width = src_width; + s->rows = height; + } + + framebuffer_update_display(surface, &s->fbsection, width, height, + src_width, surface_stride(surface), 0, + s->invalidate, fn, s, &first, &last); + if (first >= 0) { + dpy_gfx_update(s->con, 0, first, width, last - first + 1); + } + + s->invalidate = false; +} + +static void imx6ul_lcdif_invalidate_display(void *opaque) +{ + IMX6ULLCDIFState *s = opaque; + + s->invalidate = true; +} + +static const GraphicHwOps imx6ul_lcdif_graphic_ops = { + .invalidate = imx6ul_lcdif_invalidate_display, + .gfx_update = imx6ul_lcdif_update_display, +}; + +static void imx6ul_lcdif_frame_timer_cb(void *opaque) +{ + IMX6ULLCDIFState *s = opaque; + + if (!imx6ul_lcdif_is_running(s) || imx6ul_lcdif_frame_done_pending(s)) { + return; + } + + imx6ul_lcdif_frame_done(s); +} + +static uint64_t imx6ul_lcdif_read(void *opaque, hwaddr offset, unsigned size) +{ + IMX6ULLCDIFState *s = opaque; + hwaddr reg = offset & ~0xf; + uint32_t idx; + + assert(size == 4); + assert(!(offset & 0x3)); + assert(offset < LCDIF_MMIO_SIZE); + + idx = reg >> 4; + if (idx >= ARRAY_SIZE(s->regs)) { + return 0; + } + + return s->regs[idx]; +} + +static void imx6ul_lcdif_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + IMX6ULLCDIFState *s = opaque; + hwaddr reg = offset & ~0xf; + uint32_t idx; + uint32_t oldv; + + assert(size == 4); + assert(!(offset & 0x3)); + assert(offset < LCDIF_MMIO_SIZE); + + if (!imx6ul_lcdif_reg_exists(reg)) { + return; + } + + idx = reg >> 4; + oldv = s->regs[idx]; + + switch (offset & 0xf) { + case 0: + s->regs[idx] = (uint32_t)value; + break; + case REG_SET: + if (!imx6ul_lcdif_reg_has_setclr(reg)) { + return; + } + s->regs[idx] = oldv | (uint32_t)value; + break; + case REG_CLR: + if (!imx6ul_lcdif_reg_has_setclr(reg)) { + return; + } + s->regs[idx] = oldv & ~(uint32_t)value; + break; + case REG_TOG: + if (!imx6ul_lcdif_reg_has_setclr(reg)) { + return; + } + s->regs[idx] = oldv ^ (uint32_t)value; + break; + default: + g_assert_not_reached(); + } + + switch (reg) { + case A_CTRL: + if (!FIELD_EX32(oldv, CTRL, RUN) && + FIELD_EX32(s->regs[idx], CTRL, RUN)) { + s->invalidate = true; + graphic_hw_invalidate(s->con); + imx6ul_lcdif_maybe_schedule_frame(s); + break; + } + if (FIELD_EX32(oldv, CTRL, RUN) && + !FIELD_EX32(s->regs[idx], CTRL, RUN)) { + timer_del(s->frame_timer); + } + break; + case A_CTRL1: + if (FIELD_EX32(oldv, CTRL1, CUR_FRAME_DONE_IRQ) && + !FIELD_EX32(s->regs[idx], CTRL1, CUR_FRAME_DONE_IRQ)) { + imx6ul_lcdif_maybe_schedule_frame(s); + } + break; + case A_V4_TRANSFER_COUNT: + s->invalidate = true; + graphic_hw_invalidate(s->con); + break; + case A_V4_CUR_BUF: + s->invalidate = true; + graphic_hw_invalidate(s->con); + break; + case A_V4_NEXT_BUF: + s->regs[IMX6UL_LCDIF_REG_V4_CUR_BUF] = s->regs[idx]; + imx6ul_lcdif_frame_done(s); + s->invalidate = true; + graphic_hw_invalidate(s->con); + imx6ul_lcdif_maybe_schedule_frame(s); + return; + case A_AS_NEXT_BUF: + imx6ul_lcdif_frame_done(s); + imx6ul_lcdif_maybe_schedule_frame(s); + return; + default: + break; + } + + imx6ul_lcdif_update_irq(s); +} + +static const MemoryRegionOps imx6ul_lcdif_ops = { + .read = imx6ul_lcdif_read, + .write = imx6ul_lcdif_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void imx6ul_lcdif_reset(DeviceState *dev) +{ + IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev); + + memset(s->regs, 0, sizeof(s->regs)); + s->regs[IMX6UL_LCDIF_REG_CTRL1] = LCDIF_RESET_CTRL1; + s->fb_base = 0; + s->src_width = 0; + s->rows = 0; + s->src_bpp = 0; + s->invalidate = true; + timer_del(s->frame_timer); + imx6ul_lcdif_update_irq(s); +} + +static int imx6ul_lcdif_post_load(void *opaque, int version_id) +{ + IMX6ULLCDIFState *s = opaque; + + s->fb_base = 0; + s->src_width = 0; + s->rows = 0; + s->src_bpp = 0; + s->invalidate = true; + + imx6ul_lcdif_update_irq(s); + if (imx6ul_lcdif_is_running(s) && + !imx6ul_lcdif_frame_done_pending(s) && + !timer_pending(s->frame_timer)) { + imx6ul_lcdif_schedule_frame(s); + } + + return 0; +} + +static const VMStateDescription vmstate_imx6ul_lcdif = { + .name = TYPE_IMX6UL_LCDIF, + .version_id = 1, + .minimum_version_id = 1, + .post_load = imx6ul_lcdif_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, IMX6ULLCDIFState, IMX6UL_LCDIF_REGS_NUM), + VMSTATE_TIMER_PTR(frame_timer, IMX6ULLCDIFState), + VMSTATE_END_OF_LIST() + }, +}; + +static void imx6ul_lcdif_realize(DeviceState *dev, Error **errp) +{ + IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev); + + s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + imx6ul_lcdif_frame_timer_cb, s); + s->invalidate = true; + memory_region_init_io(&s->iomem, OBJECT(dev), &imx6ul_lcdif_ops, s, + TYPE_IMX6UL_LCDIF, LCDIF_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + s->con = graphic_console_init(dev, 0, &imx6ul_lcdif_graphic_ops, s); +} + +static void imx6ul_lcdif_unrealize(DeviceState *dev) +{ + IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev); + + timer_del(s->frame_timer); + timer_free(s->frame_timer); + s->frame_timer = NULL; + + if (s->con) { + graphic_console_close(s->con); + s->con = NULL; + } +} + +static void imx6ul_lcdif_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = imx6ul_lcdif_realize; + dc->unrealize = imx6ul_lcdif_unrealize; + dc->vmsd = &vmstate_imx6ul_lcdif; + device_class_set_legacy_reset(dc, imx6ul_lcdif_reset); + dc->desc = "i.MX6UL LCDIF"; +} + +static const TypeInfo imx6ul_lcdif_info = { + .name = TYPE_IMX6UL_LCDIF, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMX6ULLCDIFState), + .class_init = imx6ul_lcdif_class_init, +}; + +static void imx6ul_lcdif_register_types(void) +{ + type_register_static(&imx6ul_lcdif_info); +} + +type_init(imx6ul_lcdif_register_types) diff --git a/hw/display/meson.build b/hw/display/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -XXX,XX +XXX,XX @@ system_ss.add(when: ['CONFIG_VGA_CIRRUS', 'CONFIG_VGA_ISA'], if_true: files('cir system_ss.add(when: 'CONFIG_G364FB', if_true: files('g364fb.c')) system_ss.add(when: 'CONFIG_JAZZ_LED', if_true: files('jazz_led.c')) system_ss.add(when: 'CONFIG_PL110', if_true: files('pl110.c')) +system_ss.add(when: 'CONFIG_IMX6UL_LCDIF', if_true: files('imx6ul_lcdif.c')) system_ss.add(when: 'CONFIG_SII9022', if_true: files('sii9022.c')) system_ss.add(when: 'CONFIG_SSD0303', if_true: files('ssd0303.c')) system_ss.add(when: 'CONFIG_SSD0323', if_true: files('ssd0323.c')) diff --git a/include/hw/display/imx6ul_lcdif.h b/include/hw/display/imx6ul_lcdif.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/display/imx6ul_lcdif.h @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * i.MX6UL LCDIF controller + * + * Copyright (c) 2026 Yucai Liu <1486344514@qq.com> + */ + +#ifndef IMX6UL_LCDIF_H +#define IMX6UL_LCDIF_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" +#include "qemu/timer.h" +#include "ui/console.h" + +#define TYPE_IMX6UL_LCDIF "imx6ul-lcdif" +#define IMX6UL_LCDIF_REGS_NUM ((0x230 >> 4) + 1) +OBJECT_DECLARE_SIMPLE_TYPE(IMX6ULLCDIFState, IMX6UL_LCDIF) + +struct IMX6ULLCDIFState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegionSection fbsection; + qemu_irq irq; + QemuConsole *con; + QEMUTimer *frame_timer; + uint32_t fb_base; + uint32_t src_width; + uint32_t rows; + uint8_t src_bpp; + bool invalidate; + uint32_t regs[IMX6UL_LCDIF_REGS_NUM]; +}; + +#endif /* IMX6UL_LCDIF_H */ -- 2.43.0
From: Yucai Liu <1486344514@qq.com> Instantiate LCDIF as a child object of the i.MX6UL SoC in init and realize it in the SoC realize path before MMIO/IRQ hookup. Also make FSL_IMX6UL select CONFIG_IMX6UL_LCDIF and map the LCDIF region with a 16 KiB size to match the SoC memory map. Signed-off-by: Yucai Liu <1486344514@qq.com> Message-id: 20260412110240.93116-3-yangyanglan718@gmail.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx6ul.c | 12 ++++++++++-- include/hw/arm/fsl-imx6ul.h | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX6UL imply I2C_DEVICES select A15MPCORE select IMX + select IMX6UL_LCDIF select IMX_FEC select IMX_I2C select IMX_USBPHY diff --git a/hw/arm/fsl-imx6ul.c b/hw/arm/fsl-imx6ul.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx6ul.c +++ b/hw/arm/fsl-imx6ul.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "hw/arm/fsl-imx6ul.h" +#include "hw/display/imx6ul_lcdif.h" #include "hw/misc/unimp.h" #include "hw/usb/imx-usb-phy.h" #include "hw/core/boards.h" @@ -XXX,XX +XXX,XX @@ static void fsl_imx6ul_init(Object *obj) object_initialize_child(obj, name, &s->usb[i], TYPE_CHIPIDEA); } + /* + * LCDIF + */ + object_initialize_child(obj, "lcdif", &s->lcdif, TYPE_IMX6UL_LCDIF); + /* * SDHCIs */ @@ -XXX,XX +XXX,XX @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) /* * LCD */ - create_unimplemented_device("lcdif", FSL_IMX6UL_LCDIF_ADDR, - FSL_IMX6UL_LCDIF_SIZE); + sysbus_realize(SYS_BUS_DEVICE(&s->lcdif), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->lcdif), 0, FSL_IMX6UL_LCDIF_ADDR); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lcdif), 0, + qdev_get_gpio_in(gic, FSL_IMX6UL_LCDIF_IRQ)); /* * CSU diff --git a/include/hw/arm/fsl-imx6ul.h b/include/hw/arm/fsl-imx6ul.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx6ul.h +++ b/include/hw/arm/fsl-imx6ul.h @@ -XXX,XX +XXX,XX @@ #include "hw/net/imx_fec.h" #include "hw/usb/chipidea.h" #include "hw/usb/imx-usb-phy.h" +#include "hw/display/imx6ul_lcdif.h" #include "system/memory.h" #include "target/arm/cpu.h" #include "qom/object.h" @@ -XXX,XX +XXX,XX @@ struct FslIMX6ULState { IMX2WdtState wdt[FSL_IMX6UL_NUM_WDTS]; IMXUSBPHYState usbphy[FSL_IMX6UL_NUM_USB_PHYS]; ChipideaState usb[FSL_IMX6UL_NUM_USBS]; + IMX6ULLCDIFState lcdif; MemoryRegion rom; MemoryRegion caam; MemoryRegion ocram; @@ -XXX,XX +XXX,XX @@ enum FslIMX6ULMemoryMap { FSL_IMX6UL_PXP_SIZE = (16 * KiB), FSL_IMX6UL_LCDIF_ADDR = 0x021C8000, - FSL_IMX6UL_LCDIF_SIZE = 0x100, + FSL_IMX6UL_LCDIF_SIZE = (16 * KiB), FSL_IMX6UL_CSI_ADDR = 0x021C4000, FSL_IMX6UL_CSI_SIZE = 0x100, -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> We have a registerfields interface which we can use for defining fields alongside helpers to access them. Define the basic syndrome layout and convert the helpers that take the imm16 data directly. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-2-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 75 ++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ #ifndef TARGET_ARM_SYNDROME_H #define TARGET_ARM_SYNDROME_H -#include "qemu/bitops.h" +#include "hw/core/registerfields.h" /* Valid Syndrome Register EC field values */ enum arm_exception_class { @@ -XXX,XX +XXX,XX @@ enum arm_exception_class { EC_AA64_BKPT = 0x3c, }; +/* Generic syndrome encoding layout for HSR and lower 32 bits of ESR_EL2 */ +FIELD(SYNDROME, EC, 26, 6) +FIELD(SYNDROME, IL, 25, 1) +FIELD(SYNDROME, ISS, 0, 25) + typedef enum { SME_ET_AccessTrap, SME_ET_Streaming, @@ -XXX,XX +XXX,XX @@ typedef enum { static inline uint32_t syn_get_ec(uint32_t syn) { - return syn >> ARM_EL_EC_SHIFT; + return FIELD_EX32(syn, SYNDROME, EC); } static inline uint32_t syn_set_ec(uint32_t syn, uint32_t ec) { - return deposit32(syn, ARM_EL_EC_SHIFT, ARM_EL_EC_LENGTH, ec); + return FIELD_DP32(syn, SYNDROME, EC, ec); } /* @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_set_ec(uint32_t syn, uint32_t ec) */ static inline uint32_t syn_uncategorized(void) { - return (EC_UNCATEGORIZED << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_UNCATEGORIZED); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } +FIELD(ISS_IMM16, IMM16, 0, 16) + static inline uint32_t syn_aa64_svc(uint32_t imm16) { - return (EC_AA64_SVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA64_SVC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa64_hvc(uint32_t imm16) { - return (EC_AA64_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA64_HVC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa64_smc(uint32_t imm16) { - return (EC_AA64_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA64_SMC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_16bit) { - return (EC_AA32_SVC << ARM_EL_EC_SHIFT) | (imm16 & 0xffff) - | (is_16bit ? 0 : ARM_EL_IL); + uint32_t res = syn_set_ec(0, EC_AA32_SVC); + res = FIELD_DP32(res, SYNDROME, IL, is_16bit ? 0 : 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa32_hvc(uint32_t imm16) { - return (EC_AA32_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA32_HVC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa32_smc(void) { - return (EC_AA32_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_AA32_SMC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } static inline uint32_t syn_aa64_bkpt(uint32_t imm16) { - return (EC_AA64_BKPT << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA64_BKPT); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa32_bkpt(uint32_t imm16, bool is_16bit) { - return (EC_AA32_BKPT << ARM_EL_EC_SHIFT) | (imm16 & 0xffff) - | (is_16bit ? 0 : ARM_EL_IL); + uint32_t res = syn_set_ec(0, EC_AA32_BKPT); + res = FIELD_DP32(res, SYNDROME, IL, is_16bit ? 0 : 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa64_sysregtrap(int op0, int op1, int op2, @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_simd_access_trap(int cv, int cond, bool is_16bit) static inline uint32_t syn_sve_access_trap(void) { - return (EC_SVEACCESSTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_SVEACCESSTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } /* @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) static inline uint32_t syn_illegalstate(void) { - return (EC_ILLEGALSTATE << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_ILLEGALSTATE); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } static inline uint32_t syn_pcalignment(void) { - return (EC_PCALIGNMENT << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_PCALIGNMENT); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } static inline uint32_t syn_gcs_data_check(GCSInstructionType it, int rn) @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_gcs_gcsstr(int ra, int rn) static inline uint32_t syn_serror(uint32_t extra) { - return (EC_SERROR << ARM_EL_EC_SHIFT) | ARM_EL_IL | extra; + uint32_t res = syn_set_ec(0, EC_SERROR); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, SYNDROME, ISS, extra); + return res; } static inline uint32_t syn_mop(bool is_set, bool is_setg, int options, -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Migrate syn_aa64_sysregtrap and co-processor register trap syndromes to the registerfields API. The co-processor syndromes are split between single and duel register moves. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-3-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 124 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 102 insertions(+), 22 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ enum arm_exception_class { /* Generic syndrome encoding layout for HSR and lower 32 bits of ESR_EL2 */ FIELD(SYNDROME, EC, 26, 6) -FIELD(SYNDROME, IL, 25, 1) +FIELD(SYNDROME, IL, 25, 1) /* IL=1 for 32 bit instructions */ FIELD(SYNDROME, ISS, 0, 25) typedef enum { @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_aa64_smc(uint32_t imm16) static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_16bit) { uint32_t res = syn_set_ec(0, EC_AA32_SVC); - res = FIELD_DP32(res, SYNDROME, IL, is_16bit ? 0 : 1); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); return res; } @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_aa64_bkpt(uint32_t imm16) static inline uint32_t syn_aa32_bkpt(uint32_t imm16, bool is_16bit) { uint32_t res = syn_set_ec(0, EC_AA32_BKPT); - res = FIELD_DP32(res, SYNDROME, IL, is_16bit ? 0 : 1); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); return res; } +/* + * ISS encoding for an exception from MSR, MRS, or System instruction + * in AArch64 state. + */ +FIELD(SYSREG_ISS, ISREAD, 0, 1) /* Direction, 1 is read */ +FIELD(SYSREG_ISS, CRM, 1, 4) +FIELD(SYSREG_ISS, RT, 5, 5) +FIELD(SYSREG_ISS, CRN, 10, 4) +FIELD(SYSREG_ISS, OP1, 14, 3) +FIELD(SYSREG_ISS, OP2, 17, 3) +FIELD(SYSREG_ISS, OP0, 20, 2) + static inline uint32_t syn_aa64_sysregtrap(int op0, int op1, int op2, int crn, int crm, int rt, int isread) { - return (EC_SYSTEMREGISTERTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL - | (op0 << 20) | (op2 << 17) | (op1 << 14) | (crn << 10) | (rt << 5) - | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_SYSTEMREGISTERTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, SYSREG_ISS, OP0, op0); + res = FIELD_DP32(res, SYSREG_ISS, OP2, op2); + res = FIELD_DP32(res, SYSREG_ISS, OP1, op1); + res = FIELD_DP32(res, SYSREG_ISS, CRN, crn); + res = FIELD_DP32(res, SYSREG_ISS, RT, rt); + res = FIELD_DP32(res, SYSREG_ISS, CRM, crm); + res = FIELD_DP32(res, SYSREG_ISS, ISREAD, isread); + + return res; } +/* + * ISS encoding for an exception from an MCR or MRC access + * (move to/from co-processor) + */ +FIELD(COPROC_ISS, ISREAD, 0, 1) +FIELD(COPROC_ISS, CRM, 1, 4) +FIELD(COPROC_ISS, RT, 5, 5) +FIELD(COPROC_ISS, CRN, 10, 4) +FIELD(COPROC_ISS, OP1, 14, 3) +FIELD(COPROC_ISS, OP2, 17, 3) +FIELD(COPROC_ISS, COND, 20, 4) +FIELD(COPROC_ISS, CV, 24, 1) + static inline uint32_t syn_cp14_rt_trap(int cv, int cond, int opc1, int opc2, int crn, int crm, int rt, int isread, bool is_16bit) { - return (EC_CP14RTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc2 << 17) | (opc1 << 14) - | (crn << 10) | (rt << 5) | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_CP14RTTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, COPROC_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_ISS, OP2, opc2); + res = FIELD_DP32(res, COPROC_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_ISS, CRN, crn); + res = FIELD_DP32(res, COPROC_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_ISS, CRM, crm); + res = FIELD_DP32(res, COPROC_ISS, ISREAD, isread); + + return res; } static inline uint32_t syn_cp15_rt_trap(int cv, int cond, int opc1, int opc2, int crn, int crm, int rt, int isread, bool is_16bit) { - return (EC_CP15RTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc2 << 17) | (opc1 << 14) - | (crn << 10) | (rt << 5) | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_CP15RTTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, COPROC_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_ISS, OP2, opc2); + res = FIELD_DP32(res, COPROC_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_ISS, CRN, crn); + res = FIELD_DP32(res, COPROC_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_ISS, CRM, crm); + res = FIELD_DP32(res, COPROC_ISS, ISREAD, isread); + + return res; } +/* + * ISS encoding for an exception from an MCRR or MRRC access + * (move to/from co-processor with 2 regs) + */ +FIELD(COPROC_R2_ISS, ISREAD, 0, 1) +FIELD(COPROC_R2_ISS, CRM, 1, 4) +FIELD(COPROC_R2_ISS, RT, 5, 5) +FIELD(COPROC_R2_ISS, RT2, 10, 5) +FIELD(COPROC_R2_ISS, OP1, 16, 4) +FIELD(COPROC_R2_ISS, COND, 20, 4) +FIELD(COPROC_R2_ISS, CV, 24, 1) + static inline uint32_t syn_cp14_rrt_trap(int cv, int cond, int opc1, int crm, int rt, int rt2, int isread, bool is_16bit) { - return (EC_CP14RRTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc1 << 16) - | (rt2 << 10) | (rt << 5) | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_CP14RRTTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, COPROC_R2_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_R2_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_R2_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_R2_ISS, RT2, rt2); + res = FIELD_DP32(res, COPROC_R2_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_R2_ISS, CRM, crm); + res = FIELD_DP32(res, COPROC_R2_ISS, ISREAD, isread); + + return res; } static inline uint32_t syn_cp15_rrt_trap(int cv, int cond, int opc1, int crm, int rt, int rt2, int isread, bool is_16bit) { - return (EC_CP15RRTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc1 << 16) - | (rt2 << 10) | (rt << 5) | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_CP15RRTTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, COPROC_R2_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_R2_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_R2_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_R2_ISS, RT2, rt2); + res = FIELD_DP32(res, COPROC_R2_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_R2_ISS, CRM, crm); + res = FIELD_DP32(res, COPROC_R2_ISS, ISREAD, isread); + + return res; } static inline uint32_t syn_fp_access_trap(int cv, int cond, bool is_16bit, -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> The syn_simd_access trap was never used so remove it. We should only see the COPROC encoding on v7 architectures. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-4-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_cp15_rrt_trap(int cv, int cond, int opc1, int crm, return res; } +/* + * ISS encoding for an exception from an access to a register of + * instruction resulting from the FPEN or TFP traps. + */ +FIELD(FP_ISS, COPROC, 0, 4) /* ARMv7 only */ +FIELD(FP_ISS, COND, 20, 4) +FIELD(FP_ISS, CV, 24, 1) + static inline uint32_t syn_fp_access_trap(int cv, int cond, bool is_16bit, int coproc) { /* AArch32 FP trap or any AArch64 FP/SIMD trap: TA == 0 */ - return (EC_ADVSIMDFPACCESSTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | coproc; -} + uint32_t res = syn_set_ec(0, EC_ADVSIMDFPACCESSTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); -static inline uint32_t syn_simd_access_trap(int cv, int cond, bool is_16bit) -{ - /* AArch32 SIMD trap: TA == 1 coproc == 0 */ - return (EC_ADVSIMDFPACCESSTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (1 << 5); + res = FIELD_DP32(res, FP_ISS, CV, cv); + res = FIELD_DP32(res, FP_ISS, COND, cond); + res = FIELD_DP32(res, FP_ISS, COPROC, coproc); + + return res; } static inline uint32_t syn_sve_access_trap(void) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> For simplicity keep the OP as a two bit field rather than the two interlinked fields in the docs (ERET/ERETA). Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-5-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_sve_access_trap(void) } /* + * ISS encoding for an exception from an ERET, ERETAA or ERETAB + * instructions. + * * eret_op is bits [1:0] of the ERET instruction, so: * 0 for ERET, 2 for ERETAA, 3 for ERETAB. */ +FIELD(ERET_ISS, OP, 0, 2) + static inline uint32_t syn_erettrap(int eret_op) { - return (EC_ERETTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL | eret_op; + uint32_t res = syn_set_ec(0, EC_ERETTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ERET_ISS, OP, eret_op); + + return res; } static inline uint32_t syn_smetrap(SMEExceptionType etype, bool is_16bit) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-6-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_erettrap(int eret_op) return res; } +/* + * ISS encoding for an exception due to SME functionality + */ +FIELD(SME_ISS, SMTC, 0, 2) + static inline uint32_t syn_smetrap(SMEExceptionType etype, bool is_16bit) { - return (EC_SMETRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) | etype; + uint32_t res = syn_set_ec(0, EC_SMETRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + res = FIELD_DP32(res, SME_ISS, SMTC, etype); + + return res; } static inline uint32_t syn_pacfail(bool data, int keynumber) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> syn_pactrap is fairly simple as the ISS is all RES0. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-7-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_smetrap(SMEExceptionType etype, bool is_16bit) return res; } +/* + * ISS encoding for a PAC Fail exceptions + */ +FIELD(PACFAIL_ISS, BnA, 0, 1) /* B key or A key */ +FIELD(PACFAIL_ISS, DnI, 1, 1) /* Data or Instruction */ + static inline uint32_t syn_pacfail(bool data, int keynumber) { - int error_code = (data << 1) | keynumber; - return (EC_PACFAIL << ARM_EL_EC_SHIFT) | ARM_EL_IL | error_code; + uint32_t res = syn_set_ec(0, EC_PACFAIL); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, PACFAIL_ISS, DnI, data); + res = FIELD_DP32(res, PACFAIL_ISS, BnA, keynumber); + + return res; } +/* + * ISS encoding for an exception from a trapped Pointer + * Authentication instruction is RES0 + */ static inline uint32_t syn_pactrap(void) { - return (EC_PACTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_PACTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } static inline uint32_t syn_btitrap(int btype) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-8-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_pactrap(void) return res; } +/* + * ISS encoding for an exception from a Branch Target Identification + * instruction. + */ +FIELD(BTI_ISS, BTYPE, 0, 2) + static inline uint32_t syn_btitrap(int btype) { - return (EC_BTITRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL | btype; + uint32_t res = syn_set_ec(0, EC_BTITRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, BTI_ISS, BTYPE, btype); + return res; } static inline uint32_t syn_bxjtrap(int cv, int cond, int rm) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> This is an Armv7 specific syndrome for chips with Jazelle functionality. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-9-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_btitrap(int btype) return res; } +/* + * ISS encoding for trapped BXJ execution + * + * This is an Armv7 encoding. + */ +FIELD(BXJ_ISS, RM, 0, 4) +/* bits 4:19 are Reserved, UNK/SBZP */ +FIELD(BXJ_ISS, COND, 20, 4) +FIELD(BXJ_ISS, CV, 24, 1) + static inline uint32_t syn_bxjtrap(int cv, int cond, int rm) { - return (EC_BXJTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL | - (cv << 24) | (cond << 20) | rm; + uint32_t res = syn_set_ec(0, EC_BXJTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, BXJ_ISS, CV, cv); + res = FIELD_DP32(res, BXJ_ISS, COND, cond); + res = FIELD_DP32(res, BXJ_ISS, RM, rm); + + return res; } static inline uint32_t syn_gpc(int s2ptw, int ind, int gpcsc, int vncr, -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-10-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_bxjtrap(int cv, int cond, int rm) return res; } +/* + * ISS encoding for a Granule Protection Check exception + * + * These are only reported to EL3 + */ +FIELD(GPC_ISS, xFSC, 0, 6) +FIELD(GPC_ISS, WnR, 6, 1) /* Write not Read */ +FIELD(GPC_ISS, S1PTW, 7, 1) +FIELD(GPC_ISS, CM, 8, 1) +FIELD(GPC_ISS, VNCR, 13, 1) +FIELD(GPC_ISS, GPCSC, 14, 6) +FIELD(GPC_ISS, InD, 20, 1) /* Instruction not Data access */ +FIELD(GPC_ISS, S2PTW, 21, 1) + static inline uint32_t syn_gpc(int s2ptw, int ind, int gpcsc, int vncr, int cm, int s1ptw, int wnr, int fsc) { - return (EC_GPC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (s2ptw << 21) - | (ind << 20) | (gpcsc << 14) | (vncr << 13) | (cm << 8) - | (s1ptw << 7) | (wnr << 6) | fsc; + uint32_t res = syn_set_ec(0, EC_GPC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, GPC_ISS, S2PTW, s2ptw); + res = FIELD_DP32(res, GPC_ISS, InD, ind); + res = FIELD_DP32(res, GPC_ISS, GPCSC, gpcsc); + res = FIELD_DP32(res, GPC_ISS, VNCR, vncr); + res = FIELD_DP32(res, GPC_ISS, CM, cm); + res = FIELD_DP32(res, GPC_ISS, S1PTW, s1ptw); + res = FIELD_DP32(res, GPC_ISS, WnR, wnr); + res = FIELD_DP32(res, GPC_ISS, xFSC, fsc); + + return res; } static inline uint32_t syn_insn_abort(int same_el, int ea, int s1ptw, int fsc) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Migrate syn_insn_abort and syn_data_abort_* to the registerfields API. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Tested-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260422125250.1303100-11-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 87 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_gpc(int s2ptw, int ind, int gpcsc, int vncr, return res; } +/* + * ISS encoding for an exception from an Instruction Abort + * + * (aka instruction abort) + */ +FIELD(IABORT_ISS, IFSC, 0, 6) +FIELD(IABORT_ISS, S1PTW, 7, 1) +FIELD(IABORT_ISS, EA, 9, 1) +FIELD(IABORT_ISS, FnV, 10, 1) /* FAR not Valid */ +FIELD(IABORT_ISS, SET, 11, 2) +FIELD(IABORT_ISS, PFV, 14, 1) +FIELD(IABORT_ISS, TopLevel, 21, 1) /* FEAT_THE */ + static inline uint32_t syn_insn_abort(int same_el, int ea, int s1ptw, int fsc) { - return (EC_INSNABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | (ea << 9) | (s1ptw << 7) | fsc; + uint32_t res = syn_set_ec(0, EC_INSNABORT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, IABORT_ISS, EA, ea); + res = FIELD_DP32(res, IABORT_ISS, S1PTW, s1ptw); + res = FIELD_DP32(res, IABORT_ISS, IFSC, fsc); + + return res; } +/* + * ISS encoding for an exception from a Data Abort + */ +FIELD(DABORT_ISS, DFSC, 0, 6) +FIELD(DABORT_ISS, WNR, 6, 1) +FIELD(DABORT_ISS, S1PTW, 7, 1) +FIELD(DABORT_ISS, CM, 8, 1) +FIELD(DABORT_ISS, EA, 9, 1) +FIELD(DABORT_ISS, FnV, 10, 1) +FIELD(DABORT_ISS, LST, 11, 2) +FIELD(DABORT_ISS, VNCR, 13, 1) +FIELD(DABORT_ISS, AR, 14, 1) +FIELD(DABORT_ISS, SF, 15, 1) +FIELD(DABORT_ISS, SRT, 16, 5) +FIELD(DABORT_ISS, SSE, 21, 1) +FIELD(DABORT_ISS, SAS, 22, 2) +FIELD(DABORT_ISS, ISV, 24, 1) + static inline uint32_t syn_data_abort_no_iss(int same_el, int fnv, int ea, int cm, int s1ptw, int wnr, int fsc) { - return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL - | (fnv << 10) | (ea << 9) | (cm << 8) | (s1ptw << 7) - | (wnr << 6) | fsc; + uint32_t res = syn_set_ec(0, EC_DATAABORT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, DABORT_ISS, FnV, fnv); + res = FIELD_DP32(res, DABORT_ISS, EA, ea); + res = FIELD_DP32(res, DABORT_ISS, CM, cm); + res = FIELD_DP32(res, DABORT_ISS, S1PTW, s1ptw); + res = FIELD_DP32(res, DABORT_ISS, WNR, wnr); + res = FIELD_DP32(res, DABORT_ISS, DFSC, fsc); + + return res; } static inline uint32_t syn_data_abort_with_iss(int same_el, @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_data_abort_with_iss(int same_el, int wnr, int fsc, bool is_16bit) { - return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | ARM_EL_ISV | (sas << 22) | (sse << 21) | (srt << 16) - | (sf << 15) | (ar << 14) - | (ea << 9) | (cm << 8) | (s1ptw << 7) | (wnr << 6) | fsc; + uint32_t res = syn_set_ec(0, EC_DATAABORT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, DABORT_ISS, ISV, 1); + res = FIELD_DP32(res, DABORT_ISS, SAS, sas); + res = FIELD_DP32(res, DABORT_ISS, SSE, sse); + res = FIELD_DP32(res, DABORT_ISS, SRT, srt); + res = FIELD_DP32(res, DABORT_ISS, SF, sf); + res = FIELD_DP32(res, DABORT_ISS, AR, ar); + res = FIELD_DP32(res, DABORT_ISS, EA, ea); + res = FIELD_DP32(res, DABORT_ISS, CM, cm); + res = FIELD_DP32(res, DABORT_ISS, S1PTW, s1ptw); + res = FIELD_DP32(res, DABORT_ISS, WNR, wnr); + res = FIELD_DP32(res, DABORT_ISS, DFSC, fsc); + + return res; } /* @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_data_abort_with_iss(int same_el, */ static inline uint32_t syn_data_abort_vncr(int ea, int wnr, int fsc) { - return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (1 << ARM_EL_EC_SHIFT) - | ARM_EL_IL | ARM_EL_VNCR | (wnr << 6) | fsc; + uint32_t res = syn_set_ec(0, EC_DATAABORT_SAME_EL); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, DABORT_ISS, VNCR, 1); + res = FIELD_DP32(res, DABORT_ISS, WNR, wnr); + res = FIELD_DP32(res, DABORT_ISS, DFSC, fsc); + + return res; } static inline uint32_t syn_swstep(int same_el, int isv, int ex) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Migrate syn_swstep, syn_watchpoint and syn_breakpoint to the registerfields API. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-12-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 54 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_data_abort_vncr(int ea, int wnr, int fsc) return res; } +/* + * ISS encoding for an exception from a Software Step exception. + */ +FIELD(SOFTSTEP_ISS, IFSC, 0, 6) +FIELD(SOFTSTEP_ISS, EX, 6, 1) +FIELD(SOFTSTEP_ISS, ISV, 24, 1) + static inline uint32_t syn_swstep(int same_el, int isv, int ex) { - return (EC_SOFTWARESTEP << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | (isv << 24) | (ex << 6) | 0x22; + uint32_t res = syn_set_ec(0, EC_SOFTWARESTEP + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, SOFTSTEP_ISS, ISV, isv); + res = FIELD_DP32(res, SOFTSTEP_ISS, EX, ex); + res = FIELD_DP32(res, SOFTSTEP_ISS, IFSC, 0x22); + + return res; } +/* + * ISS encoding for an exception from a Watchpoint exception + */ +FIELD(WATCHPOINT_ISS, DFSC, 0, 6) +FIELD(WATCHPOINT_ISS, WNR, 6, 1) +FIELD(WATCHPOINT_ISS, CM, 8, 1) +FIELD(WATCHPOINT_ISS, FnV, 10, 1) +FIELD(WATCHPOINT_ISS, VNCR, 13, 1) /* FEAT_NV2 */ +FIELD(WATCHPOINT_ISS, FnP, 15, 1) +FIELD(WATCHPOINT_ISS, WPF, 16, 1) +/* bellow mandatory from FEAT_Debugv8p9 */ +FIELD(WATCHPOINT_ISS, WPTV, 17, 1) /* FEAT_Debugv8p2 - WPT valid */ +FIELD(WATCHPOINT_ISS, WPT, 18, 6) /* FEAT_Debugv8p2 - missing WP number */ + static inline uint32_t syn_watchpoint(int same_el, int cm, int wnr) { - return (EC_WATCHPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | (cm << 8) | (wnr << 6) | 0x22; + uint32_t res = syn_set_ec(0, EC_WATCHPOINT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, WATCHPOINT_ISS, CM, cm); + res = FIELD_DP32(res, WATCHPOINT_ISS, WNR, wnr); + res = FIELD_DP32(res, WATCHPOINT_ISS, DFSC, 0x22); + + return res; } +/* + * ISS encoding for an exception from a Breakpoint or a Vector Catch + * debug exception. + */ +FIELD(BREAKPOINT_ISS, IFSC, 0, 6) + static inline uint32_t syn_breakpoint(int same_el) { - return (EC_BREAKPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | 0x22; + uint32_t res = syn_set_ec(0, EC_BREAKPOINT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, BREAKPOINT_ISS, IFSC, 0x22); + + return res; } static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> This will help later when we expand the fields we report. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-13-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_breakpoint(int same_el) return res; } +/* + * ISS encoding for an exception from a WF* instruction + */ +FIELD(WFX_ISS, TI, 0, 2) +FIELD(WFX_ISS, RV, 2, 1) +FIELD(WFX_ISS, RN, 5, 5) +FIELD(WFX_ISS, COND, 20, 4) +FIELD(WFX_ISS, CV, 24, 1) + static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) { - return (EC_WFX_TRAP << ARM_EL_EC_SHIFT) | - (is_16bit ? 0 : (1 << ARM_EL_IL_SHIFT)) | - (cv << 24) | (cond << 20) | ti; + uint32_t res = syn_set_ec(0, EC_WFX_TRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, WFX_ISS, CV, cv); + res = FIELD_DP32(res, WFX_ISS, COND, cond); + res = FIELD_DP32(res, WFX_ISS, TI, ti); + + return res; } static inline uint32_t syn_illegalstate(void) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Tweak arg names to make it clear raddr is the data address register number. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-14-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_pcalignment(void) return res; } +/* + * ISS encoding for a GCS exception + * + * Field validity depends on EXTYPE + */ +FIELD(GCS_ISS, IT, 0, 5) +FIELD(GCS_ISS, RN, 5, 5) /* only for non EXLOCK exceptions */ +FIELD(GCS_ISS, RADDR, 10, 5) /* only for GCSSTR/GCSSTTR traps */ +FIELD(GCS_ISS, EXTYPE, 20, 4) + static inline uint32_t syn_gcs_data_check(GCSInstructionType it, int rn) { - return ((EC_GCS << ARM_EL_EC_SHIFT) | ARM_EL_IL | - (GCS_ET_DataCheck << 20) | (rn << 5) | it); + uint32_t res = syn_set_ec(0, EC_GCS); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, GCS_ISS, EXTYPE, GCS_ET_DataCheck); + res = FIELD_DP32(res, GCS_ISS, RN, rn); + res = FIELD_DP32(res, GCS_ISS, IT, it); + + return res; } static inline uint32_t syn_gcs_exlock(void) { - return (EC_GCS << ARM_EL_EC_SHIFT) | ARM_EL_IL | (GCS_ET_EXLOCK << 20); + uint32_t res = syn_set_ec(0, EC_GCS); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, GCS_ISS, EXTYPE, GCS_ET_EXLOCK); + + return res; } -static inline uint32_t syn_gcs_gcsstr(int ra, int rn) +static inline uint32_t syn_gcs_gcsstr(int raddr, int rn) { - return ((EC_GCS << ARM_EL_EC_SHIFT) | ARM_EL_IL | - (GCS_ET_GCSSTR_GCSSTTR << 20) | (ra << 10) | (rn << 5)); + uint32_t res = syn_set_ec(0, EC_GCS); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, GCS_ISS, EXTYPE, GCS_ET_GCSSTR_GCSSTTR); + res = FIELD_DP32(res, GCS_ISS, RADDR, raddr); + res = FIELD_DP32(res, GCS_ISS, RN, rn); + + return res; } static inline uint32_t syn_serror(uint32_t extra) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-15-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_serror(uint32_t extra) return res; } +/* + * ISS encoding for an exception from the Memory Copy and Memory Set + * instructions. + */ +FIELD(MOP_ISS, SIZEREG, 0, 5) +FIELD(MOP_ISS, SRCREG, 5, 5) +FIELD(MOP_ISS, DESTREG, 10, 5) +FIELD(MOP_ISS, FORMATOPT, 16, 2) +FIELD(MOP_ISS, OPT_A, 16, 1) +FIELD(MOP_ISS, WRONG_OPT, 17, 1) +FIELD(MOP_ISS, EPILOGUE, 18, 1) +FIELD(MOP_ISS, OPTIONS, 19, 4) +FIELD(MOP_ISS, IS_SETG, 23, 1) +FIELD(MOP_ISS, MEMINST, 24, 1) + static inline uint32_t syn_mop(bool is_set, bool is_setg, int options, bool epilogue, bool wrong_option, bool option_a, int destreg, int srcreg, int sizereg) { - return (EC_MOP << ARM_EL_EC_SHIFT) | ARM_EL_IL | - (is_set << 24) | (is_setg << 23) | (options << 19) | - (epilogue << 18) | (wrong_option << 17) | (option_a << 16) | - (destreg << 10) | (srcreg << 5) | sizereg; + uint32_t res = syn_set_ec(0, EC_MOP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, MOP_ISS, MEMINST, is_set); + res = FIELD_DP32(res, MOP_ISS, IS_SETG, is_setg); + res = FIELD_DP32(res, MOP_ISS, OPTIONS, options); + res = FIELD_DP32(res, MOP_ISS, EPILOGUE, epilogue); + res = FIELD_DP32(res, MOP_ISS, WRONG_OPT, wrong_option); + res = FIELD_DP32(res, MOP_ISS, OPT_A, option_a); + res = FIELD_DP32(res, MOP_ISS, DESTREG, destreg); + res = FIELD_DP32(res, MOP_ISS, SRCREG, srcreg); + res = FIELD_DP32(res, MOP_ISS, SIZEREG, sizereg); + + return res; } -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> It shares the same COPROC_ISS encoding as the other CP traps although not all the fields are used. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-16-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 16 ++++++++++++++++ target/arm/tcg/vfp_helper.c | 5 +---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ FIELD(COPROC_ISS, OP2, 17, 3) FIELD(COPROC_ISS, COND, 20, 4) FIELD(COPROC_ISS, CV, 24, 1) +static inline uint32_t syn_cp10_rt_trap(int cv, int cond, int opc1, + int crn, int rt, int isread) +{ + uint32_t res = syn_set_ec(0, EC_FPIDTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, COPROC_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_ISS, CRN, crn); + res = FIELD_DP32(res, COPROC_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_ISS, ISREAD, isread); + + return res; +} + static inline uint32_t syn_cp14_rt_trap(int cv, int cond, int opc1, int opc2, int crn, int crm, int rt, int isread, bool is_16bit) diff --git a/target/arm/tcg/vfp_helper.c b/target/arm/tcg/vfp_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/vfp_helper.c +++ b/target/arm/tcg/vfp_helper.c @@ -XXX,XX +XXX,XX @@ void HELPER(check_hcr_el2_trap)(CPUARMState *env, uint32_t rt, uint32_t reg) g_assert_not_reached(); } - syndrome = ((EC_FPIDTRAP << ARM_EL_EC_SHIFT) - | ARM_EL_IL - | (1 << 24) | (0xe << 20) | (7 << 14) - | (reg << 10) | (rt << 5) | 1); + syndrome = syn_cp10_rt_trap(1, 0xe, 7, reg, rt, 1); raise_exception(env, EXCP_HYP_TRAP, syndrome, 2); } -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> One more step towards dropping the old #defines. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-17-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/helper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -XXX,XX +XXX,XX @@ static void arm_cpu_do_interrupt_aarch32_hyp(CPUState *cs) */ if (cs->exception_index == EXCP_PREFETCH_ABORT || (cs->exception_index == EXCP_DATA_ABORT && - !(env->exception.syndrome & ARM_EL_ISV)) || + !FIELD_EX32(env->exception.syndrome, SYNDROME, IL)) || syn_get_ec(env->exception.syndrome) == EC_UNCATEGORIZED) { - env->exception.syndrome &= ~ARM_EL_IL; + env->exception.syndrome = FIELD_DP32(env->exception.syndrome, SYNDROME, IL, 0); } } env->cp15.esr_el[2] = env->exception.syndrome; -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> This removes the last use of ARM_EL_EC_SHIFT. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-18-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 1 - target/arm/tcg/debug.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ typedef enum { } GCSInstructionType; #define ARM_EL_EC_LENGTH 6 -#define ARM_EL_EC_SHIFT 26 #define ARM_EL_IL_SHIFT 25 #define ARM_EL_ISV_SHIFT 24 #define ARM_EL_IL (1 << ARM_EL_IL_SHIFT) diff --git a/target/arm/tcg/debug.c b/target/arm/tcg/debug.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/debug.c +++ b/target/arm/tcg/debug.c @@ -XXX,XX +XXX,XX @@ raise_exception_debug(CPUARMState *env, uint32_t excp, uint32_t syndrome) * Similarly for watchpoint and breakpoint matches. */ assert(debug_el >= cur_el); - syndrome |= (debug_el == cur_el) << ARM_EL_EC_SHIFT; + syndrome |= (debug_el == cur_el) << R_SYNDROME_EC_SHIFT; raise_exception(env, excp, syndrome, debug_el); } -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Rather than open coding a bunch of shifts and masks we can use the syndrome definitions. While we are at it assert it really is a EC_DATAABORT. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-19-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/whpx/whpx-all.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/target/arm/whpx/whpx-all.c b/target/arm/whpx/whpx-all.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/whpx/whpx-all.c +++ b/target/arm/whpx/whpx-all.c @@ -XXX,XX +XXX,XX @@ static int whpx_handle_mmio(CPUState *cpu, WHV_MEMORY_ACCESS_CONTEXT *ctx) { uint64_t syndrome = ctx->Syndrome; - bool isv = syndrome & ARM_EL_ISV; - bool iswrite = (syndrome >> 6) & 1; - bool sse = (syndrome >> 21) & 1; - uint32_t sas = (syndrome >> 22) & 3; + bool isv = FIELD_EX32(syndrome, DABORT_ISS, ISV); + bool iswrite = FIELD_EX32(syndrome, DABORT_ISS, WNR); + bool sse = FIELD_EX32(syndrome, DABORT_ISS, SSE); + uint32_t sas = FIELD_EX32(syndrome, DABORT_ISS, SAS); uint32_t len = 1 << sas; - uint32_t srt = (syndrome >> 16) & 0x1f; - uint32_t cm = (syndrome >> 8) & 0x1; + uint32_t srt = FIELD_EX32(syndrome, DABORT_ISS, SRT); + uint32_t cm = FIELD_EX32(syndrome, DABORT_ISS, CM); uint64_t val = 0; + assert(syn_get_ec(syndrome) == EC_DATAABORT); assert(!cm); assert(isv); -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Rather than open coding a bunch of shifts and masks we can use the syndrome definitions. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Tested-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260422125250.1303100-20-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/hvf/hvf.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/hvf/hvf.c +++ b/target/arm/hvf/hvf.c @@ -XXX,XX +XXX,XX @@ static int hvf_handle_exception(CPUState *cpu, hv_vcpu_exit_exception_t *excp) break; } case EC_DATAABORT: { - bool isv = syndrome & ARM_EL_ISV; - bool iswrite = (syndrome >> 6) & 1; - bool s1ptw = (syndrome >> 7) & 1; - bool sse = (syndrome >> 21) & 1; - uint32_t sas = (syndrome >> 22) & 3; + bool isv = FIELD_EX32(syndrome, DABORT_ISS, ISV); + bool iswrite = FIELD_EX32(syndrome, DABORT_ISS, WNR); + bool s1ptw = FIELD_EX32(syndrome, DABORT_ISS, S1PTW); + bool sse = FIELD_EX32(syndrome, DABORT_ISS, SSE); + uint32_t sas = FIELD_EX32(syndrome, DABORT_ISS, SAS); uint32_t len = 1 << sas; - uint32_t srt = (syndrome >> 16) & 0x1f; - uint32_t cm = (syndrome >> 8) & 0x1; + uint32_t srt = FIELD_EX32(syndrome, DABORT_ISS, SRT); + uint32_t cm = FIELD_EX32(syndrome, DABORT_ISS, CM); uint64_t val = 0; uint64_t ipa = excp->physical_address; AddressSpace *as = cpu_get_address_space(cpu, ARMASIdx_NS); -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> One more step to removing the old defines. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-21-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/tlb_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/tcg/tlb_helper.c b/target/arm/tcg/tlb_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/tlb_helper.c +++ b/target/arm/tcg/tlb_helper.c @@ -XXX,XX +XXX,XX @@ static inline uint64_t merge_syn_data_abort(uint32_t template_syn, */ assert(!fi->stage2); syn = syn_data_abort_vncr(fi->ea, is_write, fsc); - } else if (!(template_syn & ARM_EL_ISV) || target_el != 2 + } else if (!FIELD_EX32(template_syn, DABORT_ISS, ISV) || target_el != 2 || fi->s1ptw || !fi->stage2) { syn = syn_data_abort_no_iss(same_el, 0, fi->ea, 0, fi->s1ptw, is_write, fsc); -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> These are only valid for data abort syndromes. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-22-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/tlb_helper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/arm/tcg/tlb_helper.c b/target/arm/tcg/tlb_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/tlb_helper.c +++ b/target/arm/tcg/tlb_helper.c @@ -XXX,XX +XXX,XX @@ static inline uint64_t merge_syn_data_abort(uint32_t template_syn, * ST64BV, or ST64BV0 insns report syndrome info even for stage-1 * faults and regardless of the target EL. */ - if (template_syn & ARM_EL_VNCR) { + if (FIELD_EX32(template_syn, DABORT_ISS, VNCR)) { /* * FEAT_NV2 faults on accesses via VNCR_EL2 are a special case: * they are always reported as "same EL", even though we are going @@ -XXX,XX +XXX,XX @@ void arm_deliver_fault(ARMCPU *cpu, vaddr addr, * because we masked that out in disas_set_insn_syndrome()) */ bool is_vncr = (access_type != MMU_INST_FETCH) && - (env->exception.syndrome & ARM_EL_VNCR); + FIELD_EX32(env->exception.syndrome, DABORT_ISS, VNCR); if (is_vncr) { /* FEAT_NV2 faults on accesses via VNCR_EL2 go to EL2 */ -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Now everything is defined with registerfields we can drop the old defines. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-23-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ typedef enum { GCS_IT_GCSPOPX = 9, } GCSInstructionType; -#define ARM_EL_EC_LENGTH 6 -#define ARM_EL_IL_SHIFT 25 -#define ARM_EL_ISV_SHIFT 24 -#define ARM_EL_IL (1 << ARM_EL_IL_SHIFT) -#define ARM_EL_ISV (1 << ARM_EL_ISV_SHIFT) - -/* In the Data Abort syndrome */ -#define ARM_EL_VNCR (1 << 13) - static inline uint32_t syn_get_ec(uint32_t syn) { return FIELD_EX32(syn, SYNDROME, EC); -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Pass the register number (rd) to the wfit helper and report it in the syndrome ISS. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-24-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 11 ++++++++++- target/arm/tcg/helper-defs.h | 2 +- target/arm/tcg/op_helper.c | 7 ++++--- target/arm/tcg/translate-a64.c | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ FIELD(WFX_ISS, RN, 5, 5) FIELD(WFX_ISS, COND, 20, 4) FIELD(WFX_ISS, CV, 24, 1) -static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) +typedef enum { + WFI = 0b00, + WFE = 0b01, + WFIT = 0b10, + WFET = 0xb11 +} wfx_ti; + +static inline uint32_t syn_wfx(int cv, int cond, int rn, bool rv, wfx_ti ti, bool is_16bit) { uint32_t res = syn_set_ec(0, EC_WFX_TRAP); res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) res = FIELD_DP32(res, WFX_ISS, CV, cv); res = FIELD_DP32(res, WFX_ISS, COND, cond); res = FIELD_DP32(res, WFX_ISS, TI, ti); + res = FIELD_DP32(res, WFX_ISS, RN, rn); + res = FIELD_DP32(res, WFX_ISS, RV, rv); return res; } diff --git a/target/arm/tcg/helper-defs.h b/target/arm/tcg/helper-defs.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/helper-defs.h +++ b/target/arm/tcg/helper-defs.h @@ -XXX,XX +XXX,XX @@ DEF_HELPER_2(exception_pc_alignment, noreturn, env, vaddr) DEF_HELPER_1(setend, void, env) DEF_HELPER_2(wfi, void, env, i32) DEF_HELPER_1(wfe, void, env) -DEF_HELPER_2(wfit, void, env, i64) +DEF_HELPER_2(wfit, void, env, i32) DEF_HELPER_1(yield, void, env) DEF_HELPER_1(pre_hvc, void, env) DEF_HELPER_2(pre_smc, void, env, i32) diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -XXX,XX +XXX,XX @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len) env->regs[15] -= insn_len; } - raise_exception(env, excp, syn_wfx(1, 0xe, 0, insn_len == 2), + raise_exception(env, excp, syn_wfx(1, 0xe, 0, false, WFI, insn_len == 2), target_el); } @@ -XXX,XX +XXX,XX @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len) #endif } -void HELPER(wfit)(CPUARMState *env, uint64_t timeout) +void HELPER(wfit)(CPUARMState *env, uint32_t rd) { #ifdef CONFIG_USER_ONLY /* @@ -XXX,XX +XXX,XX @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) int target_el = check_wfx_trap(env, false, &excp); /* The WFIT should time out when CNTVCT_EL0 >= the specified value. */ uint64_t cntval = gt_get_countervalue(env); + uint64_t timeout = env->xregs[rd]; /* * We want the value that we would get if we read CNTVCT_EL0 from * the current exception level, so the direct_access offset, not @@ -XXX,XX +XXX,XX @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) if (target_el) { env->pc -= 4; - raise_exception(env, excp, syn_wfx(1, 0xe, 2, false), target_el); + raise_exception(env, excp, syn_wfx(1, 0xe, rd, true, WFIT, false), target_el); } if (uadd64_overflow(timeout, offset, &nexttick)) { diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/translate-a64.c +++ b/target/arm/tcg/translate-a64.c @@ -XXX,XX +XXX,XX @@ static bool trans_WFIT(DisasContext *s, arg_WFIT *a) } gen_a64_update_pc(s, 4); - gen_helper_wfit(tcg_env, cpu_reg(s, a->rd)); + gen_helper_wfit(tcg_env, tcg_constant_i32(a->rd)); /* Go back to the main loop to check for interrupts */ s->base.is_jmp = DISAS_EXIT; return true; -- 2.43.0