: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
v3->v4: Windows headers define an INT type which clashed with an enum value name in arm_gicv3_its.c... The following changes since commit eae587e8e3694b1aceab23239493fb4c7e1a80f5: Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2021-09-13' into staging (2021-09-13 11:00:30 +0100) are available in the Git repository at: https://git.linaro.org/people/pmaydell/qemu-arm.git tags/pull-target-arm-20210913-3 for you to fetch changes up to 28e987a7e7edaa3ca7feeac65edca26145df8814: hw/arm/mps2.c: Mark internal-only I2C buses as 'full' (2021-09-13 21:01:08 +0100) ---------------------------------------------------------------- target-arm queue: * mark MPS2/MPS3 board-internal i2c buses as 'full' so that command line user-created devices are not plugged into them * Take an exception if PSTATE.IL is set * Support an emulated ITS in the virt board * Add support for kudo-bmc board * Probe for KVM_CAP_ARM_VM_IPA_SIZE when creating scratch VM * cadence_uart: Fix clock handling issues that prevented u-boot from running ---------------------------------------------------------------- Bin Meng (6): hw/misc: zynq_slcr: Correctly compute output clocks in the reset exit phase hw/char: cadence_uart: Disable transmit when input clock is disabled hw/char: cadence_uart: Move clock/reset check to uart_can_receive() hw/char: cadence_uart: Convert to memop_with_attrs() ops hw/char: cadence_uart: Ignore access when unclocked or in reset for uart_{read, write}() hw/char: cadence_uart: Log a guest error when device is unclocked or in reset Chris Rauer (1): hw/arm: Add support for kudo-bmc board. Marc Zyngier (1): hw/arm/virt: KVM: Probe for KVM_CAP_ARM_VM_IPA_SIZE when creating scratch VM Peter Maydell (5): target/arm: Take an exception if PSTATE.IL is set qdev: Support marking individual buses as 'full' hw/arm/mps2-tz.c: Add extra data parameter to MakeDevFn hw/arm/mps2-tz.c: Mark internal-only I2C buses as 'full' hw/arm/mps2.c: Mark internal-only I2C buses as 'full' Richard Henderson (1): target/arm: Merge disas_a64_insn into aarch64_tr_translate_insn Shashi Mallela (9): hw/intc: GICv3 ITS initial framework hw/intc: GICv3 ITS register definitions added hw/intc: GICv3 ITS command queue framework hw/intc: GICv3 ITS Command processing hw/intc: GICv3 ITS Feature enablement hw/intc: GICv3 redistributor ITS processing tests/data/acpi/virt: Add IORT files for ITS hw/arm/virt: add ITS support in virt GIC tests/data/acpi/virt: Update IORT files for ITS docs/system/arm/nuvoton.rst | 1 + hw/intc/gicv3_internal.h | 188 ++++- include/hw/arm/virt.h | 2 + include/hw/intc/arm_gicv3_common.h | 13 + include/hw/intc/arm_gicv3_its_common.h | 32 +- include/hw/qdev-core.h | 24 + target/arm/cpu.h | 1 + target/arm/kvm_arm.h | 4 +- target/arm/syndrome.h | 5 + target/arm/translate.h | 2 + hw/arm/mps2-tz.c | 92 ++- hw/arm/mps2.c | 12 +- hw/arm/npcm7xx_boards.c | 34 + hw/arm/virt.c | 29 +- hw/char/cadence_uart.c | 61 +- hw/intc/arm_gicv3.c | 14 + hw/intc/arm_gicv3_common.c | 13 + hw/intc/arm_gicv3_cpuif.c | 7 +- hw/intc/arm_gicv3_dist.c | 5 +- hw/intc/arm_gicv3_its.c | 1322 ++++++++++++++++++++++++++++++++ hw/intc/arm_gicv3_its_common.c | 7 +- hw/intc/arm_gicv3_its_kvm.c | 2 +- hw/intc/arm_gicv3_redist.c | 153 +++- hw/misc/zynq_slcr.c | 31 +- softmmu/qdev-monitor.c | 7 +- target/arm/helper-a64.c | 1 + target/arm/helper.c | 8 + target/arm/kvm.c | 7 +- target/arm/translate-a64.c | 255 +++--- target/arm/translate.c | 21 + hw/intc/meson.build | 1 + tests/data/acpi/virt/IORT | Bin 0 -> 124 bytes tests/data/acpi/virt/IORT.memhp | Bin 0 -> 124 bytes tests/data/acpi/virt/IORT.numamem | Bin 0 -> 124 bytes tests/data/acpi/virt/IORT.pxb | Bin 0 -> 124 bytes 35 files changed, 2144 insertions(+), 210 deletions(-) create mode 100644 hw/intc/arm_gicv3_its.c create mode 100644 tests/data/acpi/virt/IORT create mode 100644 tests/data/acpi/virt/IORT.memhp create mode 100644 tests/data/acpi/virt/IORT.numamem create mode 100644 tests/data/acpi/virt/IORT.pxb